Reputation: 15141
Some BigDecimal
values can be compared with a Float
by eq
in Rspec3, but some values can't be.
describe "compare BigDecimal with Float" do
it { expect("83.79".to_d).to eq(83.79) } # => fail
it { expect("83.75".to_d).to eq(83.75) } # => succeed
end
To avoid error, I'm using expressions like eq("83.79".to_d)
.
Why does the first test fail while the second succeeds?
Upvotes: 5
Views: 2710
Reputation: 9372
"83.79".to_d is representing the fraction 8379/100 exactly in internal representation because it uses a base 10 (or a power of it), while "83.79".to_f is not because internal representation uses a base 2, so these are not equal.
That's not the same for 83.75 because is is represented exactly in both base 2 and 10 (this is 83 + 1/2 + 1/4).
If you mix big decimals and floats in the same expression, floats are then converted to the nearest big decimal... Thus, you are in fact performing this: 83.79.to_d
or put differently "83.79".to_f.to_d
Since "83.79".to_f
is not exact, and since big decimal is more accurate than float, there's no reason that it matches "83.79".to_d
.
However, if you force the conversion the other way, I would expect that equality holds:
expect("83.79".to_d.to_f).to eq(83.79)
This is because we can reasonnably expect (least astonishment) that conversions to_f will answer the nearest floating point to the exact fraction, be it from an exact big decimal or a string representation.
Upvotes: 3
Reputation: 434585
You should never try any sort of strict equality testing with floating point values. You always have to deal with inaccurate internal representation issues with Float
, so ==
and !=
aren't terribly useful.
Consider this:
'83.79'.to_d - 83.79
# => #<BigDecimal:7ff33fcea560,'-0.1E-13',9(36)>
'83.75'.to_d - 83.75
# => #<BigDecimal:7ff33fcee688,'0.0',9(27)>
Note that the difference for 83.79
is not quite zero.
If you need to compare floating point values, you always need to use a delta in your comparison; you always want to say:
Are these values within some small amount from each other?
rather than
Are these values equal?
In Rspec terms:
expect('83.75'.to_d).to be_within(1e-12).of(83.75)
expect('83.79'.to_d).to be_within(1e-12).of(83.79)
and choose the delta (1e-12
in this case) to match your requirements.
Upvotes: 5