BBC (Brian's Boot Camp)

Not to be confused with the British Broadcasting Corporation, the BBC will focus on Brian's fast paced journey of the mastery of the Java programming language.

Wednesday, February 16, 2005

Currency Conundrum

In the spirit of illustrating the inaccuracies of floating point math, this is a great example from the book Effective Java:

Question: How many items can you buy if you have $1 if each item you buy costs 10 cents more than the previous purchase, starting at 10 cents?

The answer is 4 items (one item for .10, .20, .30, and .40, all add up to $1). A pretty straight forward math problem involving only four operations and one decimal place. However if you implement the solution using doubles (the highest primitive precision floating point available) thusly:

double funds = 1.00;
int itemsBought = 0;
for (double price = 0.10; funds >= price; price += 0.10) {
funds -= price;
itemsBought++;
}

You will get the itemsBounght equal to 3. Why? Because of a cumulative rounding error even after just three operations. When the loop test comes around for the forth time price is 0.4, but funds is only 0.3999999999999999. In practice this would be rounded up to 0.4 if you were going to display it, but since it's not here, it fails the comparison test.

The moral of this story is: Be mindful of how you use floating point numbers. If exact answers are required use integral data types, or a custom object capable of holding larger scaled numbers (I.E. BigDecimal).

Since money is an important thing to get right, when I implemented the accounting part of Iris, I wrote the Money class as a wrapper around BigDecimal. All math operations using money use the Money class. There have been several (well, 2) occassions where other parties have come up with different invoice amounts when manually calculating invoices out of Iris -- and Iris has proved to be right in both cases.

Here's the answer to that programming problem using the Money class:

int itemsBought = 0;
Money funds = new Money(1.0);
for (Money price = new Money(0.1); funds.compareTo(price) >= 0;
price = price.add(new Money (0.1))) {

  itemsBought++;
  funds = funds.subtract(price);
}

Which gives the correct answer of 4.

2 Comments:

Blogger Brian T. Grant said...

If you look at my first comment on this post the answer that I eventually came up with produced a final figure of 20272.000000000004. I didn't understand that then and I'm not sure I understand it now. It doesn't seem a lot to ask to have some simple math done. I can put the same calculations in that app through a calculator and I come up with the right number; why can't Java? Maybe it isn't a problem with Java, per se, but it seems like something that just shouldn't happen either way.

11:36 AM  
Blogger Sten said...

The floating point "problem" is systemic to binary computers -- your calculator being no exception. It transcends language, C, Java, FORTRAN, VB, they all have to deal with it.

The issue in a nutshell can be sumarized as "how do you represent an infinite number of real numbers in a finite number of bits? (32 in the case of a float, 64 in a double)."

The classic example is that 0.1 cannot be represented exactly in base 2 (Binary -- which is how your computer does math and represents numbers). So when the computer is asked to display 0.1, it is really rounding its very close approximation.

Calculators come up with the "right" answer either because 1) they don't have enough precision and they round the number, giving you the impression that it's right or 2) they are geared toward base 10 so they will display base 10 numbers rounded accordingly.

Basically though, your calculator is "right" because it's not precise enough.

You can check this out if you want more info. Basically though, the thing to take from this is, in practice:

1)You generally never display a floating point number in all its "full glory". You always round it to a couple decimal places.

2)You never rely on a floating point number being exactly correct, so you never compare them with exact equalities (unless you round them first).

2:58 PM  

Post a Comment

<< Home