Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Internal rounding woes: accurate way to sum Ruby floating point numbers?

Tags:

ruby

This is of course broken:

(0.1 + 0.1 + 0.1) => 0.30000000000000004

(0.1 + 0.1 + 0.1) == 0.3   # false

I don't need a perfect sum, just good enough to say two Floats are the same value. The best I can figure out is to multiply both sides of the equation and round. Is this the best way?

((0.1 + 0.1 + 0.1) * 1000).round == (0.3 * 1000).round

UPDATE: I'm stuck on Ruby v1.8.7.

like image 547
Amy Avatar asked Mar 15 '13 09:03

Amy


1 Answers

There is a difference between summing accurately and comparing effectively. You say you want the former, but it looks like you want the later. The underlying Ruby float arithmetic is IEEE and has sensible semantics for minimizing accumulated error, but there always will be when using a representation that can't exactly represent all values. To accurately model error, FP addition shouldn't produce an exact value, it should produce an interval and further additions will operate on intervals.

In practice, many applications don't need to have detailed accounting for error, they just need to do their calculation and be aware that comparisons aren't exact and output decimal representations should be rounded.

Here's a simple extension to Float that will help you out with comparison. It or something like it should be in the stdlib, but ain't.

class Float
  def near_enough?(other, epsilon = 1e-6)
    (self - other.to_f).abs < epsilon.to_f
  end
end

pry(main)> (0.1 + 0.1 + 0.1).near_enough?(0.3)
=> true
pry(main)> (0.1 + 0.1 + 0.1).near_enough?(0.3, 1e-17)
=> false
pry(main)> ( [0.1] * (10**6) ).reduce(:+).near_enough?(10**5, 1e-5)
=> true
pry(main)> ( [0.1] * (10**6) ).reduce(:+).near_enough?(10**5)
=> false

Picking an appropriate epsilon can be tricky in the general case. You should read What Every Computer Scientist Should Know About Floating-Point Arithmetic. I've found Bruce Dawson's floating point tricks blogs excellent, here's his chapter on Comparing Floating Point Numbers

If you really are concerned about accuracy, you could do your arithmetic using an exact representation. Ruby supplies a Rational class (even back in 1.8) which let's you do exact arithmetic on fractions.

pry(main)> r=Rational(1,10)
=> (1/10)
pry(main)> (r + r + r) == Rational(3,10)
=> true
pry(main)> (r + r + r) == 0.3
=> true
pry(main)> r.to_f
=> 0.1
pry(main)> (r + r + r).to_f
=> 0.3
like image 186
dbenhur Avatar answered Nov 15 '22 04:11

dbenhur