为什么 10^9942066 是我可以计算而不会溢出的最大幂?
Why is 10^9942066 the biggest power I can calculate without overflows?
在ruby中,一些大数大于无穷大。通过二分查找,我发现:
(1.0/0) > 10**9942066.000000001 # => false
(1.0/0) > 10**9942066 # => true
RUBY_VERSION # => "2.3.0"
这是为什么? 109942066有什么特别之处?好像不是9999999那样的任意数,不接近2的任意次方(约等于233026828.36662442)。
为什么ruby的无穷大不是无穷大? 109942066是如何参与的?
我现在意识到,任何大于 109942066 的数都会溢出到无穷大:
10**9942066.000000001 #=> Infinity
10**9942067 #=> Infinity
但这仍然留下问题:为什么 109942066?
TL;DR
我在 numeric.c 的 int_pow
中手动进行了计算,检查整数溢出(以及对 Bignum 的传播,包括对 rb_big_pow
的调用)发生的位置。一旦对 rb_big_pow
的调用发生,就会检查你在 int_pow
中得到的两个中间值是否太大,并且截止值似乎就在 9942066 左右(如果你使用以 10 为底数的幂)。这个值大约接近
BIGLEN_LIMIT / ceil(log2(base^n)) * n ==
32*1024*1024 / ceil(log2(10^16)) * 16 ==
32*1024*1024 / 54 * 16 ~=
9942054
其中 BIGLEN_LIMIT
是 ruby 中的内部限制,用作检查幂计算是否过大的常量,定义为 32*1024*1024
。 base
是 10,n
是仍然适合 Fixnum 的基数的最大 2 次方指数。
不幸的是,由于用于计算大数幂的算法,我找不到比这种近似更好的方法,但如果您的代码需要在之前检查有效性,它可能足以用作上限对大数求幂。
原题:
问题不在于 9942066,而在于您的一个数字是整数,另一个是浮点数。所以
(10**9942066).class # => Bignum
(10**9942066.00000001).class # => Float
第一个在内部用特定数字表示,小于Infinity
。第二个,因为它仍然是一个浮点数,不能用实际数字表示,只是简单地用 Infinity
代替,当然不大于 Infinity
.
更新问题:
你是对的,9942066 似乎有些不同(如果你在 Linux 下使用 64 位 ruby,因为在其他系统下限制可能不同)。虽然 ruby 确实使用 GMP 库来处理大数字,但它甚至在进入 GMP 之前会进行一些预检查,如您收到的警告所示。它还将使用 GMP 的 mul 命令手动求幂,而不调用 GMP 的 pow 函数。
幸运的是警告很容易捕捉到:
irb(main):010:0> (10**9942066).class
=> Bignum
irb(main):005:0> (10**9942067).class
(irb):5: warning: in a**b, b may be too big
=> Float
然后您可以实际检查这些警告在 ruby 的 bignum.c 库中发出的位置。
但首先我们需要进入 Bignum 领域,因为我们的两个数字都是简单的 Fixnums。计算的初始部分,以及从 fixnum 到 bignum 的 "upgrade" 是在 numeric.c 内部完成的。 Ruby 进行快速求幂,并在每一步检查结果是否仍适合 Fixnum(比系统位大小少 2 位:64 位机器上的 62 位)。如果不是,它会将值转换为 Bignum 领域,并在那里继续计算。我们对这种转换发生的时间点很感兴趣,所以让我们试着找出它在我们的 10^9942066
示例中发生的时间(我使用 ruby 中存在的 x、y、z 变量numeric.c代码):
x = 10^1 z = 10^0 y = 9942066
x = 10^2 z = 10^0 y = 4971033
x = 10^2 z = 10^2 y = 4971032
x = 10^4 z = 10^2 y = 2485516
x = 10^8 z = 10^2 y = 1242758
x = 10^16 z = 10^2 y = 621379
x = 10^16 z = 10^18 y = 621378
x = OWFL
此时x会溢出(10^32 > 2^62-1
),所以进程会在Bignum领域继续计算x**y
,也就是(10^16)^621378
(实际上还是两个Fixnums在这个阶段)
如果您现在返回 bignum.c 并检查它如何确定一个数字是否太大,您会看到它将检查容纳 x
所需的位数,并将此数字乘以 y
。如果结果大于 32*1024*1024
,它将失败(发出警告并使用基本浮点数进行计算)。
(10^16)
是54位(ceil(log_2(10^16)) == 54
),54*621378
是33554412。这只比33554432略小(乘以20),ruby之后的极限不会进行 Bignum 求幂,而只是将 y
转换为双倍,并希望最好的结果(这显然会失败,只是 return Infinity
)
现在让我们尝试用 9942067 检查一下:
x = 10^1 z = 10^0 y = 9942067
x = 10^1 z = 10^1 y = 9942066
x = 10^2 z = 10^1 y = 4971033
x = 10^2 z = 10^3 y = 4971032
x = 10^4 z = 10^3 y = 2485516
x = 10^8 z = 10^3 y = 1242758
x = 10^16 z = 10^3 y = 621379
x = 10^16 z = OWFL
这里,在z溢出点(10^19 > 2^62-1
),计算将在Bignum领域继续,并计算x**y
。注意这里会计算(10^16)^621379
,而(10^16)
仍然是54位,54*621379
是33554466,比33554432大了34位。由于它更大,您会收到警告,并且 ruby 只会使用双精度进行计算,因此结果是 Infinity
.
请注意,只有在使用幂函数时才会进行这些检查。这就是为什么你仍然可以做 (10**9942066)*10
,因为在做普通乘法时不存在类似的检查,这意味着你可以在 ruby 中实现你自己的快速求幂方法,在这种情况下它仍然可以使用更大的值,尽管您将不再进行此安全检查。例如,请参阅此快速实施:
def unbounded_pow(x,n)
if n < 0
x = 1.0 / x
n = -n
end
return 1 if n == 0
y = 1
while n > 1
if n.even?
x = x*x
n = n/2
else
y = x*y
x = x*x
n = (n-1)/2
end
end
x*y
end
puts (10**9942066) == (unbounded_pow(10,9942066)) # => true
puts (10**9942067) == (unbounded_pow(10,9942067)) # => false
puts ((10**9942066)*10) == (unbounded_pow(10,9942067)) # => true
但是我怎么知道特定碱基的截止值?
我的数学不是很好,但我可以说出一种方法来估计截止值的位置。如果检查上面的调用,您可以看到 Fixnum 和 Bignum 之间的转换发生在中间基数达到 Fixnum 的限制时。这个阶段的中间底总是有一个指数,它是 2 的幂,所以你只需要最大化这个值。例如,让我们尝试计算出 12 的最大截止值。
首先我们必须检查我们可以存储在 Fixnum 中的最高基数是多少:
ceil(log2(12^1)) = 4
ceil(log2(12^2)) = 8
ceil(log2(12^4)) = 15
ceil(log2(12^8)) = 29
ceil(log2(12^16)) = 58
ceil(log2(12^32)) = 115
我们可以看到 12^16
是我们可以存储在 62 位中的最大值,或者如果我们使用的是 32 位机器 12^8
将适合 30 位(ruby' s Fixnums 最多可以存储比机器大小限制小两位的值。
对于12^16
,我们可以很容易地确定截止值。它将是 32*1024*1024 / ceil(log2(12^16))
,也就是 33554432 / 58 ~= 578525
。我们现在可以在 ruby 中轻松检查:
irb(main):004:0> ((12**16)**578525).class
=> Bignum
irb(main):005:0> ((12**16)**578526).class
(irb):5: warning: in a**b, b may be too big
=> Float
现在我们不想回到 12
的原始基地。截止点将在 578525*16
左右(16 是新基数的指数),即 9256400
。如果您签入 ruby,这些值实际上非常接近这个数字:
irb(main):009:0> (12**9256401).class
=> Bignum
irb(main):010:0> (12**9256402).class
(irb):10: warning: in a**b, b may be too big
=> Float
请注意,问题不在于数字,而在于操作,如您收到的警告所述。
$ ruby -e 'puts (1.0/0) > 10**9942067'
-e:1: warning: in a**b, b may be too big
false
问题是 10**9942067
破坏了 Ruby 的幂函数。它不是抛出异常,而是错误地导致无穷大。
$ ruby -e 'puts 10**9942067'
-e:1: warning: in a**b, b may be too big
Infinity
.
10**9942067
不大于无穷大,它错误地导致无穷大。这是很多数学图书馆的一个坏习惯,让数学家沮丧地挖出他们的眼球。
无穷大不大于无穷大,它们相等,所以你的大于检查是错误的。您可以通过检查它们是否相等来查看这一点。
$ ruby -e 'puts (1.0/0) == 10**9942067'
-e:1: warning: in a**b, b may be too big
true
将此与直接使用科学记数法指定数字进行对比。现在 Ruby 不必对大数进行数学运算,它只知道任何实数都小于无穷大。
$ ruby -e 'puts (1.0/0) > 10e9942067'
false
现在你可以放任意大的指数了。
$ ruby -e 'puts (1.0/0) > 10e994206700000000000000000000000000000000'
false
在ruby中,一些大数大于无穷大。通过二分查找,我发现:
(1.0/0) > 10**9942066.000000001 # => false
(1.0/0) > 10**9942066 # => true
RUBY_VERSION # => "2.3.0"
这是为什么? 109942066有什么特别之处?好像不是9999999那样的任意数,不接近2的任意次方(约等于233026828.36662442)。
为什么ruby的无穷大不是无穷大? 109942066是如何参与的?
我现在意识到,任何大于 109942066 的数都会溢出到无穷大:
10**9942066.000000001 #=> Infinity
10**9942067 #=> Infinity
但这仍然留下问题:为什么 109942066?
TL;DR
我在 numeric.c 的 int_pow
中手动进行了计算,检查整数溢出(以及对 Bignum 的传播,包括对 rb_big_pow
的调用)发生的位置。一旦对 rb_big_pow
的调用发生,就会检查你在 int_pow
中得到的两个中间值是否太大,并且截止值似乎就在 9942066 左右(如果你使用以 10 为底数的幂)。这个值大约接近
BIGLEN_LIMIT / ceil(log2(base^n)) * n ==
32*1024*1024 / ceil(log2(10^16)) * 16 ==
32*1024*1024 / 54 * 16 ~=
9942054
其中 BIGLEN_LIMIT
是 ruby 中的内部限制,用作检查幂计算是否过大的常量,定义为 32*1024*1024
。 base
是 10,n
是仍然适合 Fixnum 的基数的最大 2 次方指数。
不幸的是,由于用于计算大数幂的算法,我找不到比这种近似更好的方法,但如果您的代码需要在之前检查有效性,它可能足以用作上限对大数求幂。
原题:
问题不在于 9942066,而在于您的一个数字是整数,另一个是浮点数。所以
(10**9942066).class # => Bignum
(10**9942066.00000001).class # => Float
第一个在内部用特定数字表示,小于Infinity
。第二个,因为它仍然是一个浮点数,不能用实际数字表示,只是简单地用 Infinity
代替,当然不大于 Infinity
.
更新问题:
你是对的,9942066 似乎有些不同(如果你在 Linux 下使用 64 位 ruby,因为在其他系统下限制可能不同)。虽然 ruby 确实使用 GMP 库来处理大数字,但它甚至在进入 GMP 之前会进行一些预检查,如您收到的警告所示。它还将使用 GMP 的 mul 命令手动求幂,而不调用 GMP 的 pow 函数。
幸运的是警告很容易捕捉到:
irb(main):010:0> (10**9942066).class
=> Bignum
irb(main):005:0> (10**9942067).class
(irb):5: warning: in a**b, b may be too big
=> Float
然后您可以实际检查这些警告在 ruby 的 bignum.c 库中发出的位置。
但首先我们需要进入 Bignum 领域,因为我们的两个数字都是简单的 Fixnums。计算的初始部分,以及从 fixnum 到 bignum 的 "upgrade" 是在 numeric.c 内部完成的。 Ruby 进行快速求幂,并在每一步检查结果是否仍适合 Fixnum(比系统位大小少 2 位:64 位机器上的 62 位)。如果不是,它会将值转换为 Bignum 领域,并在那里继续计算。我们对这种转换发生的时间点很感兴趣,所以让我们试着找出它在我们的 10^9942066
示例中发生的时间(我使用 ruby 中存在的 x、y、z 变量numeric.c代码):
x = 10^1 z = 10^0 y = 9942066
x = 10^2 z = 10^0 y = 4971033
x = 10^2 z = 10^2 y = 4971032
x = 10^4 z = 10^2 y = 2485516
x = 10^8 z = 10^2 y = 1242758
x = 10^16 z = 10^2 y = 621379
x = 10^16 z = 10^18 y = 621378
x = OWFL
此时x会溢出(10^32 > 2^62-1
),所以进程会在Bignum领域继续计算x**y
,也就是(10^16)^621378
(实际上还是两个Fixnums在这个阶段)
如果您现在返回 bignum.c 并检查它如何确定一个数字是否太大,您会看到它将检查容纳 x
所需的位数,并将此数字乘以 y
。如果结果大于 32*1024*1024
,它将失败(发出警告并使用基本浮点数进行计算)。
(10^16)
是54位(ceil(log_2(10^16)) == 54
),54*621378
是33554412。这只比33554432略小(乘以20),ruby之后的极限不会进行 Bignum 求幂,而只是将 y
转换为双倍,并希望最好的结果(这显然会失败,只是 return Infinity
)
现在让我们尝试用 9942067 检查一下:
x = 10^1 z = 10^0 y = 9942067
x = 10^1 z = 10^1 y = 9942066
x = 10^2 z = 10^1 y = 4971033
x = 10^2 z = 10^3 y = 4971032
x = 10^4 z = 10^3 y = 2485516
x = 10^8 z = 10^3 y = 1242758
x = 10^16 z = 10^3 y = 621379
x = 10^16 z = OWFL
这里,在z溢出点(10^19 > 2^62-1
),计算将在Bignum领域继续,并计算x**y
。注意这里会计算(10^16)^621379
,而(10^16)
仍然是54位,54*621379
是33554466,比33554432大了34位。由于它更大,您会收到警告,并且 ruby 只会使用双精度进行计算,因此结果是 Infinity
.
请注意,只有在使用幂函数时才会进行这些检查。这就是为什么你仍然可以做 (10**9942066)*10
,因为在做普通乘法时不存在类似的检查,这意味着你可以在 ruby 中实现你自己的快速求幂方法,在这种情况下它仍然可以使用更大的值,尽管您将不再进行此安全检查。例如,请参阅此快速实施:
def unbounded_pow(x,n)
if n < 0
x = 1.0 / x
n = -n
end
return 1 if n == 0
y = 1
while n > 1
if n.even?
x = x*x
n = n/2
else
y = x*y
x = x*x
n = (n-1)/2
end
end
x*y
end
puts (10**9942066) == (unbounded_pow(10,9942066)) # => true
puts (10**9942067) == (unbounded_pow(10,9942067)) # => false
puts ((10**9942066)*10) == (unbounded_pow(10,9942067)) # => true
但是我怎么知道特定碱基的截止值?
我的数学不是很好,但我可以说出一种方法来估计截止值的位置。如果检查上面的调用,您可以看到 Fixnum 和 Bignum 之间的转换发生在中间基数达到 Fixnum 的限制时。这个阶段的中间底总是有一个指数,它是 2 的幂,所以你只需要最大化这个值。例如,让我们尝试计算出 12 的最大截止值。
首先我们必须检查我们可以存储在 Fixnum 中的最高基数是多少:
ceil(log2(12^1)) = 4
ceil(log2(12^2)) = 8
ceil(log2(12^4)) = 15
ceil(log2(12^8)) = 29
ceil(log2(12^16)) = 58
ceil(log2(12^32)) = 115
我们可以看到 12^16
是我们可以存储在 62 位中的最大值,或者如果我们使用的是 32 位机器 12^8
将适合 30 位(ruby' s Fixnums 最多可以存储比机器大小限制小两位的值。
对于12^16
,我们可以很容易地确定截止值。它将是 32*1024*1024 / ceil(log2(12^16))
,也就是 33554432 / 58 ~= 578525
。我们现在可以在 ruby 中轻松检查:
irb(main):004:0> ((12**16)**578525).class
=> Bignum
irb(main):005:0> ((12**16)**578526).class
(irb):5: warning: in a**b, b may be too big
=> Float
现在我们不想回到 12
的原始基地。截止点将在 578525*16
左右(16 是新基数的指数),即 9256400
。如果您签入 ruby,这些值实际上非常接近这个数字:
irb(main):009:0> (12**9256401).class
=> Bignum
irb(main):010:0> (12**9256402).class
(irb):10: warning: in a**b, b may be too big
=> Float
请注意,问题不在于数字,而在于操作,如您收到的警告所述。
$ ruby -e 'puts (1.0/0) > 10**9942067'
-e:1: warning: in a**b, b may be too big
false
问题是 10**9942067
破坏了 Ruby 的幂函数。它不是抛出异常,而是错误地导致无穷大。
$ ruby -e 'puts 10**9942067'
-e:1: warning: in a**b, b may be too big
Infinity
10**9942067
不大于无穷大,它错误地导致无穷大。这是很多数学图书馆的一个坏习惯,让数学家沮丧地挖出他们的眼球。
无穷大不大于无穷大,它们相等,所以你的大于检查是错误的。您可以通过检查它们是否相等来查看这一点。
$ ruby -e 'puts (1.0/0) == 10**9942067'
-e:1: warning: in a**b, b may be too big
true
将此与直接使用科学记数法指定数字进行对比。现在 Ruby 不必对大数进行数学运算,它只知道任何实数都小于无穷大。
$ ruby -e 'puts (1.0/0) > 10e9942067'
false
现在你可以放任意大的指数了。
$ ruby -e 'puts (1.0/0) > 10e994206700000000000000000000000000000000'
false