斐波那契问题导致算术溢出
Fibonacci problem causes Arithmetic overflow
问题:创建一个只有一个输入的函数。 Return 包含斐波那契数列(从 0 开始)的数组的索引,其元素与函数的输入匹配。
16 ~ │ def fib(n)
17 ~ │ return 0 if n == 0
18 │
19 ~ │ last = 0u128
20 ~ │ current = 1u128
21 │
22 ~ │ (n - 1).times do
23 ~ │ last, current = current, last + current
24 │ end
25 + │
26 + │ current
27 │ end
28 │
60 │ def usage
61 │ progname = String.new(ARGV_UNSAFE.value)
62 │
63 │ STDERR.puts <<-H
64 │ #{progname} <integer>
65 │ Given Fibonacci; determine which fib value would
66 │ exist at <integer> index.
67 │ H
68 │
69 │ exit 1
70 │ end
71 │
72 │ if ARGV.empty?
73 │ usage
74 │ end
75 │
76 │ begin
77 ~ │ i = ARGV[0].to_i
78 ~ │ puts fib i
79 │ rescue e
80 │ STDERR.puts e
81 │ usage
82 │ end
我对这个问题的解决方式一点也不优雅,我是在凌晨 2 点累的时候做的。所以我不是在寻找更优雅的解决方案。我很好奇的是,如果我 运行 结果应用程序的输入大于 45,那么我会看到 Arithmetic overflow
。我想我的变量输入有问题。我 运行 在 Ruby 中使用它并且它 运行 很好所以我知道这不是硬件问题...
谁能帮我找出我做错了什么?我还在挖,也是。我这周才开始使用 Crystal。这是我第二次 application/experiment 使用它。我很喜欢,但我还不知道它的一些特质。
编辑
已更新脚本以反映建议的更改以及上述更改 运行 时间的结果。通过上述更改,我现在可以 运行 程序成功超过 45,但最多只能达到 90 左右。这很有趣。我将 运行 通过这个,看看我可能需要在何处添加额外的显式转换。似乎非常不直观的是,在启动时更改类型并没有 "stick" 整个 运行 时间,我首先尝试但失败了。有些东西对我来说没有意义。
原始结果
$ crystal build fib.cr
$ ./fib 45
1836311903
$ ./fib 46
Arithmetic overflow
$ ./fib.rb 460
985864329041134079854737521712801814394706432953315\
510410398508752777354792040897021902752675861
最新结果
$ ./fib 92
12200160415121876738
$ ./fib 93
Arithmetic overflow
./fib <integer>
Given Fibonacci; determine which fib value would
exist at <integer> index.
编辑^2
现在也决定可能 ARGV[0]
是问题所在。所以我将对 f()
的调用更改为:
62 begin
63 i = ARGV[0].to_u64.as(UInt64)
64 puts f i
65 rescue e
66 STDERR.puts e
67 usage
68 end
并添加了调试打印以显示正在使用的变量类型:
22 return 0 if p == 0
23
24 puts "p: %s\tfib_now: %s\tfib_last: %s\tfib_hold: %s\ti: %s" % [typeof(p), typeof(fib_now), typeof(fib_last), typeof(fib_hold), typeof(i)]
25 loop do
p: UInt64 fib_now: UInt64 fib_last: UInt64 fib_hold: UInt64 i: UInt64
Arithmetic overflow
./fib <integer>
Given Fibonacci; determine which fib value would
exist at <integer> index.
编辑^3
在 Jonne 修复错误解决方案后更新了最新代码。事实证明,问题是即使使用 128 位无符号整数,我也达到了结构的极限。 Ruby 优雅地处理了这个问题。看来在crystal,还是得靠我优雅的处理了。
Crystal 中的默认整数类型是 Int32
,因此如果您没有明确指定整数文字的类型,您会得到它。
特别是台词
fib_last = 0
fib_now = 1
将变量转换为有效类型Int32
。要解决此问题,请确保指定这些整数的类型,因为您不需要负数,UInt64
在这里似乎最合适:
fib_last = 0u64
fib_now = 1u64
另请注意我在此处使用的文字语法。您的 0.to_i64
创建了一个 In32
,然后从中创建了一个 Int64
。编译器足够聪明,可以在发布版本的编译时进行这种转换,但我认为只使用文字语法更好。
编辑更新问题的答案
Fibonacci 定义为 F0 = 0,F1 = 1,Fn = Fn-2 + Fn-1, 所以 0, 1, 1, 2, 3, 5.
你的算法差了一个。它为给定的 n > 1 计算 Fn+1,换句话说,0、1、2、3、5,换句话说,它基本上跳过 F2 。
这是一个正确的做法:
def fib(n)
return 0 if n == 0
last = 0u64
current = 1u64
(n - 1).times do
last, current = current, last + current
end
current
end
这为 F92 正确给出了 7540113804746346429,为 F93 正确给出了 12200160415121876738。但是对于 F94 它仍然会溢出,因为那将是 19740274219868223167,它大于 264 = 18446744073709551616,所以它不适合 UInt64
。再次澄清一下,您的版本在被要求计算 F93 时尝试计算 F94,因此您得到了 "too early"。
所以如果你想支持计算 Fn for n > 93 那么你需要冒险进入实验 Int128
/UInt128
支持或使用BigInt
.
除了整数文字默认为 Int32
.
之外,我认为还应该提到一件事来解释 Ruby/Crystal 的区别。
在Ruby动态类型解释型语言中,没有变量类型的概念,只有值类型。所有变量都可以保存任何类型的值。
这允许它在 Fixnum
溢出时在幕后透明地将 Bignum
转换为 Bignum
。
Crystal 相反是一种静态类型的编译语言,由于类型推断和类型联合,它看起来和感觉像 Ruby,但变量本身是类型化的。
这允许它在编译时捕获大量错误,并以类似 C 的速度捕获类似 运行 Ruby 的代码。
我 认为 ,但不要相信我的话,Crystal 理论上可以匹配 Ruby 的行为,但它会麻烦多于好处。它需要对整数的所有操作 return 与 BigInt
的类型联合,在这一点上,为什么不单独保留基本类型,并在必要时直接使用大整数。
长话短说,如果您需要使用超出 UInt128
可以容纳的非常大的整数值,require "big"
并声明相关变量 BigInt
.
编辑:另见 here 极端情况,显然 BigInt
s 也会溢出(我从来不知道)但有一个简单的补救措施。
问题:创建一个只有一个输入的函数。 Return 包含斐波那契数列(从 0 开始)的数组的索引,其元素与函数的输入匹配。
16 ~ │ def fib(n)
17 ~ │ return 0 if n == 0
18 │
19 ~ │ last = 0u128
20 ~ │ current = 1u128
21 │
22 ~ │ (n - 1).times do
23 ~ │ last, current = current, last + current
24 │ end
25 + │
26 + │ current
27 │ end
28 │
60 │ def usage
61 │ progname = String.new(ARGV_UNSAFE.value)
62 │
63 │ STDERR.puts <<-H
64 │ #{progname} <integer>
65 │ Given Fibonacci; determine which fib value would
66 │ exist at <integer> index.
67 │ H
68 │
69 │ exit 1
70 │ end
71 │
72 │ if ARGV.empty?
73 │ usage
74 │ end
75 │
76 │ begin
77 ~ │ i = ARGV[0].to_i
78 ~ │ puts fib i
79 │ rescue e
80 │ STDERR.puts e
81 │ usage
82 │ end
我对这个问题的解决方式一点也不优雅,我是在凌晨 2 点累的时候做的。所以我不是在寻找更优雅的解决方案。我很好奇的是,如果我 运行 结果应用程序的输入大于 45,那么我会看到 Arithmetic overflow
。我想我的变量输入有问题。我 运行 在 Ruby 中使用它并且它 运行 很好所以我知道这不是硬件问题...
谁能帮我找出我做错了什么?我还在挖,也是。我这周才开始使用 Crystal。这是我第二次 application/experiment 使用它。我很喜欢,但我还不知道它的一些特质。
编辑
已更新脚本以反映建议的更改以及上述更改 运行 时间的结果。通过上述更改,我现在可以 运行 程序成功超过 45,但最多只能达到 90 左右。这很有趣。我将 运行 通过这个,看看我可能需要在何处添加额外的显式转换。似乎非常不直观的是,在启动时更改类型并没有 "stick" 整个 运行 时间,我首先尝试但失败了。有些东西对我来说没有意义。
原始结果
$ crystal build fib.cr
$ ./fib 45
1836311903
$ ./fib 46
Arithmetic overflow
$ ./fib.rb 460
985864329041134079854737521712801814394706432953315\
510410398508752777354792040897021902752675861
最新结果
$ ./fib 92
12200160415121876738
$ ./fib 93
Arithmetic overflow
./fib <integer>
Given Fibonacci; determine which fib value would
exist at <integer> index.
编辑^2
现在也决定可能 ARGV[0]
是问题所在。所以我将对 f()
的调用更改为:
62 begin
63 i = ARGV[0].to_u64.as(UInt64)
64 puts f i
65 rescue e
66 STDERR.puts e
67 usage
68 end
并添加了调试打印以显示正在使用的变量类型:
22 return 0 if p == 0
23
24 puts "p: %s\tfib_now: %s\tfib_last: %s\tfib_hold: %s\ti: %s" % [typeof(p), typeof(fib_now), typeof(fib_last), typeof(fib_hold), typeof(i)]
25 loop do
p: UInt64 fib_now: UInt64 fib_last: UInt64 fib_hold: UInt64 i: UInt64
Arithmetic overflow
./fib <integer>
Given Fibonacci; determine which fib value would
exist at <integer> index.
编辑^3
在 Jonne 修复错误解决方案后更新了最新代码。事实证明,问题是即使使用 128 位无符号整数,我也达到了结构的极限。 Ruby 优雅地处理了这个问题。看来在crystal,还是得靠我优雅的处理了。
Crystal 中的默认整数类型是 Int32
,因此如果您没有明确指定整数文字的类型,您会得到它。
特别是台词
fib_last = 0
fib_now = 1
将变量转换为有效类型Int32
。要解决此问题,请确保指定这些整数的类型,因为您不需要负数,UInt64
在这里似乎最合适:
fib_last = 0u64
fib_now = 1u64
另请注意我在此处使用的文字语法。您的 0.to_i64
创建了一个 In32
,然后从中创建了一个 Int64
。编译器足够聪明,可以在发布版本的编译时进行这种转换,但我认为只使用文字语法更好。
编辑更新问题的答案
Fibonacci 定义为 F0 = 0,F1 = 1,Fn = Fn-2 + Fn-1, 所以 0, 1, 1, 2, 3, 5. 你的算法差了一个。它为给定的 n > 1 计算 Fn+1,换句话说,0、1、2、3、5,换句话说,它基本上跳过 F2 。
这是一个正确的做法:
def fib(n)
return 0 if n == 0
last = 0u64
current = 1u64
(n - 1).times do
last, current = current, last + current
end
current
end
这为 F92 正确给出了 7540113804746346429,为 F93 正确给出了 12200160415121876738。但是对于 F94 它仍然会溢出,因为那将是 19740274219868223167,它大于 264 = 18446744073709551616,所以它不适合 UInt64
。再次澄清一下,您的版本在被要求计算 F93 时尝试计算 F94,因此您得到了 "too early"。
所以如果你想支持计算 Fn for n > 93 那么你需要冒险进入实验 Int128
/UInt128
支持或使用BigInt
.
除了整数文字默认为 Int32
.
在Ruby动态类型解释型语言中,没有变量类型的概念,只有值类型。所有变量都可以保存任何类型的值。
这允许它在 Fixnum
溢出时在幕后透明地将 Bignum
转换为 Bignum
。
Crystal 相反是一种静态类型的编译语言,由于类型推断和类型联合,它看起来和感觉像 Ruby,但变量本身是类型化的。
这允许它在编译时捕获大量错误,并以类似 C 的速度捕获类似 运行 Ruby 的代码。
我 认为 ,但不要相信我的话,Crystal 理论上可以匹配 Ruby 的行为,但它会麻烦多于好处。它需要对整数的所有操作 return 与 BigInt
的类型联合,在这一点上,为什么不单独保留基本类型,并在必要时直接使用大整数。
长话短说,如果您需要使用超出 UInt128
可以容纳的非常大的整数值,require "big"
并声明相关变量 BigInt
.
编辑:另见 here 极端情况,显然 BigInt
s 也会溢出(我从来不知道)但有一个简单的补救措施。