2

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?

DarenW
  • 16,549
  • 7
  • 63
  • 102
bryantsai
  • 3,405
  • 1
  • 30
  • 30

4 Answers4

5

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
  • 19,893
  • 7
  • 54
  • 51
  • 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 Dec 23 '09 at 05:58
  • 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 Dec 23 '09 at 06:17
2

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.

Community
  • 1
  • 1
Peter
  • 127,331
  • 53
  • 180
  • 211
  • Wouldn't it affect some value just slightly blow .5? – bryantsai Dec 23 '09 at 04:56
  • 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 Dec 23 '09 at 05:01
1

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.

alony
  • 10,725
  • 3
  • 39
  • 46
DarenW
  • 16,549
  • 7
  • 63
  • 102
0

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
  • 29,124
  • 6
  • 56
  • 53
  • 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 Dec 23 '09 at 05:05