Reputation: 163
First, take a specific float f
:
f = [64.4, 73.60, 77.90, 87.40, 95.40].sample # take any one of these special Floats
f.to_d.class == (1.to_d * f).class # => true (BigDecimal)
So multiplying by BigDecimal
casts f
to BigDecimal
. Therefore 1.to_d * f
(or f * 1.to_d
) can be seen as a (poor, but still) form of converting f
to BigDecimal
. And yet for these specific values we have:
f.to_d == 1.to_d * f # => false (?!)
Isn't this a bug? I'd assume that while multiplying by 1.to_d
Ruby should invoke f.to_d
internally. But the results differ, i.e. for f = 64.4
:
f.to_d # => #<BigDecimal:7f8202038280,'0.644E2',18(36)>
1.to_d * f # => #<BigDecimal:7f82019c1208,'0.6440000000 000001E2',27(45)>
I cannot see why floating-point representation error should be an excuse here, yet it's obviously a cause, somehow. So why is this happening?
PS. I wrote a snippet of code playing around with this issue:
https://github.com/Swarzkopf314/ruby_wtf/blob/master/multiplication_by_unit.rb
Upvotes: 6
Views: 4540
Reputation: 114178
So why is this happening?
TL;DR different precisions are used.
Long answer:
64.4.to_d
calls bigdecimal/util
's Float#to_d
:
def to_d(precision=nil)
BigDecimal(self, precision || Float::DIG)
end
Unless specified, it uses an implicit precision of Float::DIG
which is 15
for current implementations:
Float::DIG
#=> 15
So 64.4.to_d
is equivalent to:
BigDecimal(64.4, Float::DIG)
#=> #<BigDecimal:7fd7cc0aa838,'0.644E2',18(36)>
BigDecimal#*
on the other hand converts a given float argument via:
if (RB_TYPE_P(r, T_FLOAT)) {
b = GetVpValueWithPrec(r, DBL_DIG+1, 1);
}
DBL_DIG
is the C-equivalent of Float::DIG
, so it's basically:
BigDecimal(64.4, Float::DIG + 1)
#=> #<BigDecimal:7fd7cc098408,'0.6440000000 000001E2',27(36)>
That said, you can get the expected result if you provide the precision explicitly, either:
f.to_d(16) == 1.to_d * f
#=> true
or:
f.to_d == 1.to_d.mult(f, 15)
#=> true
and of course by explicitly converting f
via to_d
:
f.to_d == 1.to_d * f.to_d
#=> true
Isn't this a bug?
It looks like one, you should file a bug report.
Note that neither 0.644E2
, nor 0.6440000000000001E2
is an exact representation of the given floating point number. As already noted by Eli Sadoff, 64.4
's exact value is 64.400000000000005684341886080801486968994140625
, so the most exact BigDecimal
representation would be:
BigDecimal('64.400000000000005684341886080801486968994140625')
#=> #<BigDecimal:7fd7cc04a0c8,'0.6440000000 0000005684 3418860808 0148696899 4140625E2',54(63)>
IMO, 64.4.to_d
should return just that.
Upvotes: 5
Reputation: 7308
This is not a bug. f == f.to_d
returns false
, so if f == 1.to_d * f
is true, then f.to_d == 1.to_d * f
must be false
because f != f.to_d
. The ==
method for BigDecimal
is intended to compare BigDecimal
s not BigDecimal
to float
. Sometimes the equality will work, but for some f
s the BigDecimal
representation is exact whereas the float
is not.
Edit: See Is Floating Point Math Broken for more of an explanation.
Upvotes: 3