为什么10 ^ 9942066是我可以在没有溢出的情况下计算的最大功率?

时间:2016-04-14 18:50:52

标签: ruby int biginteger

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

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

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

为什么红宝石的无限无限?如何涉及10 9942066

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

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

但仍然存在这样的问题:为什么10 9942066

2 个答案:

答案 0 :(得分:11)

<强> TL; DR

我手动在numeric.c&#39; s int_pow内完成了计算,检查整数溢出的位置(以及传播到Bignum的内容,包括对{{1}的调用})发生。一旦调用rb_big_pow,就会检查rb_big_pow中的两个中间值是否过大,截止值似乎只有9942066左右(如果你& #39;使用10的基数作为电源)。大约这个值接近

int_pow

其中BIGLEN_LIMIT / ceil(log2(base^n)) * n == 32*1024*1024 / ceil(log2(10^16)) * 16 == 32*1024*1024 / 54 * 16 ~= 9942054 是ruby中的内部限制,用作检查功率计算是否过大的常量,并定义为BIGLEN_LIMIT32*1024*1024为10,base是基数的最大幂指数,仍然适合Fixnum。

不幸的是,由于用于计算大数字功率的算法,我找不到比这种近似更好的方法,但是如果你的代码需要在之前检查有效性,那么它可能足够用作上限对大数字进行取幂。

原始问题:

问题不在于9942066,而是您的一个数字是整数,另一个是浮点数。所以

n

第一个可由内部特定数字表示,小于(10**9942066).class # => Bignum (10**9942066.00000001).class # => Float 。第二个,因为它仍然是一个浮点数不能用实际数字表示,只是被Infinity取代,当然不会大于Infinity

更新了问题:

你是正确的,在9942066周围似乎有些不同(如果你在Linux下使用64位ruby,因为在其他系统下限制可能会有所不同)。虽然ruby确实使用GMP库来处理大数字,但在进入GMP之前它会进行一些预先检查,如您可以收到的警告所示。它还将使用GMP的mul命令手动进行取幂,而无需调用GMP的pow函数。

幸运的是,这些警告很容易被发现:

Infinity

然后你可以实际检查ruby的bignum.c库中发出这些警告的位置。

但首先我们需要进入Bignum领域,因为我们的两个数字都是简单的Fixnums。计算的初始部分,以及&#34;升级&#34;从fixnum到bignum是在numeric.c内完成的。 Ruby执行快速取幂,并在每一步检查结果是否仍然适合Fixnum(比系统bitsize小2位:64位机器上的62位)。如果没有,它会将值转换为Bignum领域,并在那里继续计算。我们感兴趣的是这种转换发生的地方,所以让我们试着弄清楚它在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 示例中的作用(我使用x,y,z变量存在于ruby中) #39; s numeric.c代码):

10^9942066

此时x将溢出(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 ),因此该过程将通过计算10^32 > 2^62-1继续在Bignum领域,x**y(实际上它们仍然是这两个Fixnums)阶段)

如果你现在回到bignum.c并检查它是如何确定一个数字是否太大,你可以看到它将检查保持(10^16)^621378所需的位数,并乘以这个号码与x。如果结果大于y,则它将失败(发出警告并使用基本浮点数进行计算)。

32*1024*1024为54位((10^16)),ceil(log_2(10^16)) == 54为33554412.这仅略小于33554432(按20),此后红宝石不会进行Bignum取幂的极限,但只需将54*621378转换为双倍,并希望最好(显然会失败,只返回y

现在让我们尝试使用9942067进行检查:

Infinity

此处,在z溢出(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 )处,计算将在Bignum领域继续,并将计算10^19 > 2^62-1。请注意,此处它将计算x**y,而(10^16)^621379仍为54位,(10^16)为33554466,大于33554432(按34)。因为它越大,你就会得到警告,而ruby只会使用double进行计算,结果是54*621379

请注意,只有在使用电源功能时才会执行这些检查。这就是为什么你仍然可以做Infinity的原因,因为在进行简单乘法时不存在类似的检查,这意味着你可以在ruby中实现自己的快速取幂方法,在这种情况下它仍然可以使用更大的值,虽然你不再进行这种安全检查了。例如,参见这个快速实现:

(10**9942066)*10

但我怎么知道特定基数的截止值?

我的数学并不是很好,但我可以告诉一种方法来估算截止值的位置。如果检查上述调用,您可以看到Fixnum和Bignum之间的转换发生在中间基数达到Fixnum的限制时。此阶段的中间基数将始终具有2的幂的指数,因此您只需最大化此值。例如,让我们试着找出12的最大截止值。

首先,我们必须检查我们可以在Fixnum中存储的最高基数:

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

我们可以看到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 是我们可以存储在62位中的最大值,或者如果我们使用32位机器12^16将适合30位(ruby&#39; fixnums)可以存储最小比机器大小限制小两位的值。

对于12^8,我们可以轻松确定截止值。它将是12^16,即32*1024*1024 / ceil(log2(12^16))。我们现在可以在ruby中轻松检查:

33554432 / 58 ~= 578525

现在我们不想回到我们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附近(16是新基数的指数),即578525*16。如果您签入ruby,则值实际上非常接近此数字:

9256400

答案 1 :(得分:2)

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

$ 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

The other answer explains why this happens near 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