Ruby 1.8.7中的指数运算返回错误答案



我在irb中尝试计算3**557时遇到了这个问题。Ruby和MacRuby都安装在我的Mac(OS X 10.8)中。Ruby的版本是1.8.7,MacRuby 0.12(Ruby 1.9.2)。rib和macirb在计算3**557时给了我两个不同的答案。(macirb是对的。)

$ irb
>> 3**557
=> 54755702179342762063551440788945541007926808765326951193810107165429610423703291760740244724326099993131913104272587572918520442872536889724676586931200965615875242243330408150984753872526006744122187638040962508934109837755428764447134683114539218909666971979603
$ macirb
irb(main):001:0> 3**557
=> 57087217942658063217290581978966727348872586279944803346410228520919738045995056049600505293676159316424182057188730248707922985741467061108015301244570536546607487919981026877250949414156613856336341922395385463291076789878575326012378057561766997352898452974964563

然后我尝试了一些更大的东西,比如3**5337,这次我得到了同样的答案。

那么,这是Ruby 1.8.7中的一个错误,还是我应该使用另一种方法来计算幂?

在计算时,当数字超过Fixnum的界限时,Ruby应该从Fixnum转换为Bignum。对于旧版本的Ruby,使用**运算符会失败

$ ruby --version
ruby 1.8.7 (2012-02-08 patchlevel 358) [universal-darwin12.0]
$ irb
>> 2 ** 62
=> 4611686018427387904
>> 2 ** 63
=> -9223372036854775808
>> 2 ** 64
=> 0

它在哪里失败取决于体系结构的字大小。在本例中,iMac上的64位字。在内部,Fixnum被强制转换为一个长整数,并且运算符是用long处理的。long以单词大小溢出,而Ruby通过返回0来不礼貌地处理这一问题。

请注意,*运算符工作正常(转换为Bignum),其中**失败:

>> a = 2 ** 62
=> 4611686018427387904
>> 2 ** 63
=> -9223372036854775808
>> a * 2
=> 9223372036854775808
>> 2 ** 64
=> 0
>> a * 4
=> 18446744073709551616

使用Ruby的新版本可以解决这个问题。如果你不能移动到一个新的版本,那么避免使用Fixnum和**的大功率。

使用1.9.3会产生正确的结果。除非你有充分的理由,否则尽量使用1.9.3或更好的版本,因为1.8.7正在逐步淘汰。

同样值得注意的是,在Linux上使用1.8.7-p358进行测试后,我也得到了正确的答案。它可能是您正在使用的1.8.7特定版本中的一个错误。

这绝对是一个bug。它可能取决于处理器和/或编译选项。

如果这次提交修复了它,我不会感到惊讶。

正如其他人所说,现在只有安全修复才能升级到1.8.7,所以升级到1.9.3。

它与显式求幂无关。我认为这在某种程度上与表示所需的从63位到64位的转换有关,尽管这似乎不是100%一致的。

>> 19**14
=> 799006685782884121
>> 19**15
=> -3265617043834753317
>> (19**14)*19
=> -3265617043834753317

>> 2**64-1
=> -1
>> 2**64
=> 0
>> 0x7fffffffffffffff
=> 9223372036854775807

然而

>> 0x8000000000000000
=> 9223372036854775808

另外:在32位模式下运行irb(arch -i386 irb),我现在没有看到这一点,但之前:

>> 19**15
=> 15181127029874798299
>> 2**31
=> -2147483648

编写自己的求幂方法似乎是另一种不会产生错误的方法:

def xpnt(base, exponent)
    sum = base
    while exponent >= 2
        sum = sum * base
        exponent -= 1
    end
    puts sum
end

任何幂的"10"都应该以单个"1"开头,后面只有零。Ruby的**函数:

10 ** 40
=> 10000000000000000000092233720368547758080

自定义xpnt方法:

xpnt 10, 40
10000000000000000000000000000000000000000
=> nil

最新更新