问题 为什么10 ^ 9942066是我能计算出的最大功率而没有溢出?


在红宝石中,一些大数字大于无穷大。通过二进制搜索,我发现:

(1.0/0) > 10**9942066.000000001 # => false
(1.0/0) > 10**9942066 # => true
RUBY_VERSION # => "2.3.0"

为什么是这样? 10有什么特别之处9942066?它似乎不是像9999999那样的任意数字,它不接近任何两个的幂(它与2大致相等)33026828.36662442)。

为什么红宝石的无限无限?怎么样109942066 参与?


我现在意识到,任何大于10的数字9942066 将溢出到无穷大:

10**9942066.000000001 #=> Infinity
10**9942067 #=> Infinity

但这仍然留下了一个问题: 为什么109942066


4370
2018-04-14 18:50


起源

Ruby的无穷大在数学意义上显然不是无限的。根据定义,它可以处理的最大数量。 - sawa
在数字意义上无法表示真正的无穷大。比较事物与“无限”实际上并不是真正有用的 1/0 是未定义的,而不是“无限”,因为Ruby会让你相信。如果Ruby在数学上是正确的,那么我们最终可以回答“什么是无限加一?”的问题。 - tadman
@sawa显然。但是,它仍然存在9942066来自何处的问题。 - Shelvacu
评估它需要多长时间?我的电脑正在挂,计算 10**9942066。 - sawa
@sawa嗯,确实很熟悉。 - steenslag


答案:


TL; DR

我在里面完成了计算 numeric.cint_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*1024base 是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的“升级”在内部完成 numeric.c。 Ruby执行快速取幂,并在每一步检查结果是否仍然适合Fixnum(比系统bitsize小2位:64位机器上的62位)。如果没有,它会将值转换为Bignum领域,并在那里继续计算。我们感兴趣的是这种转换发生的地方,所以让我们试着弄清楚它在我们的时间 10^9942066示例(我在ruby的numeric.c代码中使用x,y,z变量):

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)) == 5454*621378 是33554412.这只是略小于33554432(按20),之后红宝石不会做Bignum取幂的极限,而只是转换 y 加倍,希望最好(显然会失败,然后回归 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只会用double计算,因此结果是 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的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

11
2018-04-14 19:09



但它也发生在10 ^ 9942067,也溢出到无限。但为什么9942066? - Shelvacu
10**9942066 不是最大可能值,因为 10**9942066 * 100 工作得很好。 (注意:编辑后的数字) - Shelvacu
我无法理解您最近的编辑。 :/ - Shelvacu
“当基数为10”时,截止值似乎在9942066附近,但是 为什么?? 是否有一些特殊的溢出数,10 ** 9942067的结果与之相交..66不是?如何在不进行二分查找的情况下计算其他数字的溢出位置? - Shelvacu


请注意,问题不在于数字,而在于操作,正如您收到的警告所述。

$ 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

另一个答案解释了为什么这发生在10e9942067附近

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

2
2018-04-14 22:27