views:

255

answers:

4

I understand due to the inexact representation of floating points, the following code 'feels' inconsistent.

"%.1f" % 1.14 # => 1.1
"%.1f" % 1.15 # => 1.1
"%.1f" % 1.16 # => 1.2
"%.0f" % 1.4 # => 1
"%.0f" % 1.5 # => 2
"%.0f" % 1.6 # => 2

However, is there an easy way of doing consistent floating points rounding by 5? One way might be to do string manipulation explicitly. Is there an easier way or existent library?

A: 

Multiply by 100, then round, then divide by 100:

(1.15 * 100).round / 100.0 # => 1.15

It's not exactly elegant, but it avoids using strings.

Alex Reisner
this isn't right - there's still a problem, because `1.15` is now represented internally, and imprecisely, using floating-point. Again, as soon as you come to display it, you'll get answers you don't expect.
Peter
Ack! Of course you're right. Don't know what I was thinking...
Alex Reisner
+2  A: 

Just add a tiny pertubation, to ensure things that are just under 0.5 in floating-point become just over.

For example,

x = 1.15
"%.1f" % (1.000001*x)  # include correction for imprecise floating-point.

this will be enough to deal with the formatting problems, while very unlikely to cause a relevant error.

also: an obvious follow-on to my earlier question here, which is fine, but included for completeness.

Peter
Wouldn't it affect some value just slightly blow .5?
bryantsai
Well, the value `(1/1.000001)*(1.1499999999)` will be incorrectly rounded, but seeing as you're only printing values for display, I think this is fine. If this is a problem, just add some more zeros.
Peter
+4  A: 

If you want decimal precision, use BigDecimal instead of floats.

Edit: You will have to manually round the number to the desired length before passing it to %, otherwise it gets converted to a normal float before being rounded.

"%.1f" % BigDecimal('1.15').round(1) => "1.2"
"%.0f" % BigDecimal('1.5').round(0) => "2"
Tobias Cohen
Yeah, simple and elegant.
bryantsai
a side question, is x.to_s for a float always yield x (like 1.15.to_s => '1.15')? or would also affected by the inexact representation?
bryantsai
Sorry, I'm not sure about that one. From my tests, everything seems to come through OK, however Float#to_s does seem to truncate any number with more than 15 digits.
Tobias Cohen
+1  A: 

The function roundthis() in this example shows how to round numbers in a controllable, consistent way. Note the small fudge value. Try running this example without the fudge to see what happens.

def roundthis(x, m)
    return (x/m+0.50001).floor*m
end

for x in [1.14, 1.15, 1.16]
    print "#{x}   #{roundthis(x, 0.1)}  \n"
end

for x in [1.4, 1.5, 1.6]
    print "#{x}   #{roundthis(x, 1.0)}  \n"
end

This, put into a file named roundtest.rb and executed prints

bash> ruby roundtest.rb
1.14   1.1  
1.15   1.2  
1.16   1.2  
1.4   1.0  
1.5   2.0  
1.6   2.0

Note the ease of rounding to the nearest 2, 15, 0.005, or whatever.

DarenW