Exponentiation in Ruby 1.8.7 Returns Wrong Answers
-
27-06-2021 - |
Question
I met this problem when I tried to compute 3**557 in irb. Ruby and MacRuby both are installed in my Mac (OS X 10.8). And the version of ruby is 1.8.7, of MacRuby 0.12 (ruby 1.9.2). rib and macirb gave me two different answers on computation of 3**557. (macirb's is right.)
$ irb
>> 3**557
=> 54755702179342762063551440788945541007926808765326951193810107165429610423703291760740244724326099993131913104272587572918520442872536889724676586931200965615875242243330408150984753872526006744122187638040962508934109837755428764447134683114539218909666971979603
$ macirb
irb(main):001:0> 3**557
=> 57087217942658063217290581978966727348872586279944803346410228520919738045995056049600505293676159316424182057188730248707922985741467061108015301244570536546607487919981026877250949414156613856336341922395385463291076789878575326012378057561766997352898452974964563
And then I tried something bigger, e.g. 3**5337, and I got the same answer this time.
So, is this a bug in Ruby 1.8.7, or I should use another way to compute exponentiation?
Solution
When calculating, Ruby is supposed to convert from Fixnum to Bignum when the numbers go beyond the bounds of Fixnum. For older versions of Ruby, this fails with the ** operator:
$ ruby --version
ruby 1.8.7 (2012-02-08 patchlevel 358) [universal-darwin12.0]
$ irb
>> 2 ** 62
=> 4611686018427387904
>> 2 ** 63
=> -9223372036854775808
>> 2 ** 64
=> 0
Where it fails depends on the word size of the architecture. 64-bit words on the iMac in this example. Internally, the Fixnum is cast to a long integer, and the operator is handled with longs. The longs overflow at word size, and Ruby is ungracefully handling this by returning 0.
Note that the * operator works correctly (converting to Bignum), where the ** fails:
>> a = 2 ** 62
=> 4611686018427387904
>> 2 ** 63
=> -9223372036854775808
>> a * 2
=> 9223372036854775808
>> 2 ** 64
=> 0
>> a * 4
=> 18446744073709551616
Moving to a newer version of Ruby will fix this. If you can't move to a newer version, then avoid using Fixnum and ** with large powers.
OTHER TIPS
Using 1.9.3 produces the correct result. Unless you have a really good reason, try to use 1.9.3 or better since 1.8.7 is being phased out.
It's also worth noting that after testing with 1.8.7-p358 on Linux I get the correct answer as well. it could be a bug in the particular version of 1.8.7 you're using.
This is definitely a bug. It is probably dependent on the processor and/or compilation options.
I wouldn't be surprised if it was fixed by this commit.
As others have stated, only security fixes make it to 1.8.7 nowadays, so upgrade to 1.9.3.
It's not related to exponentiation explicitly. I think it's in some way related to the transition from 63 to 64 bits required for representation, though this doesn't seem 100% consistent.
>> 19**14
=> 799006685782884121
>> 19**15
=> -3265617043834753317
>> (19**14)*19
=> -3265617043834753317
and
>> 2**64-1
=> -1
>> 2**64
=> 0
>> 0x7fffffffffffffff
=> 9223372036854775807
yet
>> 0x8000000000000000
=> 9223372036854775808
Also: running irb in 32-bit mode (arch -i386 irb
), I don't see this at this point, but earlier:
>> 19**15
=> 15181127029874798299
>> 2**31
=> -2147483648
Writing your own exponentiation method seems to be another way to do it that doesn't produce errors:
def xpnt(base, exponent)
sum = base
while exponent >= 2
sum = sum * base
exponent -= 1
end
puts sum
end
'10' to any power should begin with a single '1' and be followed by nothing but zeros.
Ruby's **
function:
10 ** 40
=> 10000000000000000000092233720368547758080
Custom xpnt
method:
xpnt 10, 40
10000000000000000000000000000000000000000
=> nil