ironsand
ironsand

Reputation: 15141

Some `BigDecimal` values don't match with `Float`

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

Answers (2)

aka.nice
aka.nice

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

mu is too short
mu is too short

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

Related Questions