1

The universal advice to avoid floating point errors in ruby is to use BigDecimal. I must be overlooking something, because I think I've found a case where BigDecimal math is returning an error where a Float does not:

using Float gives the correct answer of 2.75:

> 50.0 * 0.6 / 360.0 * 33
=> 2.75

using BigDecimal gives the incorrect answer of 2.74999999:

> BigDecimal("50") * BigDecimal("0.6") / BigDecimal("360") * BigDecimal("33")
=> #<BigDecimal:7efe74824c80,'0.2749999999 999999989E1',27(36)>

Someone please tell me what I'm missing here?

Mladen Jablanović
  • 43,461
  • 10
  • 90
  • 113
Phil R
  • 330
  • 1
  • 3
  • 10
  • Is your question why the BigDecimal gives the error, or why the Float doesn't? – Mladen Jablanović Jul 20 '16 at 20:03
  • Why does BigDecimal give the error? And does this example mean that we can no longer trust BigDecimal with our floating point calculations? – Phil R Jul 20 '16 at 20:16
  • 1
    See http://stackoverflow.com/q/588004/128421. You misunderstand floating point math. – the Tin Man Jul 20 '16 at 20:29
  • FWIW, if you can, first multiply before you divide: `BigDecimal("50") * BigDecimal("0.6") * BigDecimal("33") / BigDecimal("360")` and you get your desired result. The answer by @Mladen already tells you why you don't get an exact result if you do it your way. If you first multiply, **the chances are higher** you get an exact result. The line above does `990 / 360` (or `(11 * 90) / (4 * 90)`), which does not produce any repeating decimals. – Rudy Velthuis Jul 22 '16 at 16:49

1 Answers1

3

Let's simplify your example, and use this one instead:

BigDecimal(1) / BigDecimal(3) * BigDecimal(3)
# => #<BigDecimal:19289d8,'0.9999999999 99999999E0',18(36)>

How did it get there?

BigDecimal(1) / BigDecimal(3)
# => #<BigDecimal:1921a70,'0.3333333333 33333333E0',18(36)>

BigDecimal does not provide rational numbers, so when you divide 1 by 3, you get 0, following by a lot of 3s. A lot, but not infinitely many. When you then multiply that by 3, you will get 0 followed by equally many 9s.

I believe you misread the BigDecimal's advertisement (although I am not sure it is anywhere advertised as the solution to floating point errors). It just provides arbitrary precision. It is still a floating point number. If you really want exact numbers when dividing numbers, you might take a look at Rational class:

(Rational(50) * Rational(0.6) / Rational(360) * Rational(33)).to_f
# => 2.75
Mladen Jablanović
  • 43,461
  • 10
  • 90
  • 113
  • Since 2.1 the `r` suffix can be used: `50r * Rational(0.6) / 360r * 33r`. – cremno Jul 20 '16 at 20:41
  • Thank you Mladen. I'm writing a financial app, and considered all the following types for representing money: * Integer, storing amounts in cents: no good because we need to be able to store fractions of a cent. * Money gem: uses integer, so same problem as integer. * Float: prone to floating point errors. * BigDecimal: less prone to floating point errors (but now I find, still prone to errors in one of our use cases!) Maybe Rational is the answer then.. – Phil R Jul 20 '16 at 20:57
  • 1
    Another solution which seems to work for me: BigDecimal("50") * BigDecimal("0.6") * BigDecimal("33") / BigDecimal("360") This is based on a comment in the thread that Jordan marked this a duplicate of: Actually, the key to doing such calculations accurately isn't using fixed-point arithmetic, but rather arranging calculations to avoid having to round values which are going to be scaled up. For example, to charge someone for 17 days of a 30 day month, multiply the monthly amount by 17 and then divide by 30, rather than dividing by 30 and multiplying by 17. – supercat Oct 1 '14 at 15:13 – Phil R Jul 20 '16 at 21:53