I’ve always been told never to represent money with
float types, and this time I pose the question to you: why?
I’m sure there is a very good reason, I simply do not know what it is.
Because floats and doubles cannot accurately represent the base 10 multiples that we use for money. This issue isn’t just for Java, it’s for any programming language that uses base 2 floating-point types.
In base 10, you can write 10.25 as 1025 * 10-2 (an integer times a power of 10). IEEE-754 floating-point numbers are different, but a very simple way to think about them is to multiply by a power of two instead. For instance, you could be looking at 164 * 2-4 (an integer times a power of two), which is also equal to 10.25. That’s not how the numbers are represented in memory, but the math implications are the same.
Even in base 10, this notation cannot accurately represent most simple fractions. For instance, you can’t represent 1/3: the decimal representation is repeating (0.3333…), so there is no finite integer that you can multiply by a power of 10 to get 1/3. You could settle on a long sequence of 3’s and a small exponent, like 333333333 * 10-10, but it is not accurate: if you multiply that by 3, you won’t get 1.
However, for the purpose of counting money, at least for countries whose money is valued within an order of magnitude of the US dollar, usually all you need is to be able to store multiples of 10-2, so it doesn’t really matter that 1/3 can’t be represented.
The problem with floats and doubles is that the vast majority of money-like numbers don’t have an exact representation as an integer times a power of 2. In fact, the only multiples of 0.01 between 0 and 1 (which are significant when dealing with money because they’re integer cents) that can be represented exactly as an IEEE-754 binary floating-point number are 0, 0.25, 0.5, 0.75 and 1. All the others are off by a small amount. As an analogy to the 0.333333 example, if you take the floating-point value for 0.01 and you multiply it by 10, you won’t get 0.1. Instead you will get something like 0.099999999786…
Representing money as a
float will probably look good at first as the software rounds off the tiny errors, but as you perform more additions, subtractions, multiplications and divisions on inexact numbers, errors will compound and you’ll end up with values that are visibly not accurate. This makes floats and doubles inadequate for dealing with money, where perfect accuracy for multiples of base 10 powers is required.
A solution that works in just about any language is to use integers instead, and count cents. For instance, 1025 would be $10.25. Several languages also have built-in types to deal with money. Among others, Java has the
BigDecimal class, and C# has the
From Bloch, J., Effective Java, (2nd ed, Item 48. 3rd ed, Item 60):
particularly ill-suited for monetary
calculations because it is impossible
to represent 0.1 (or any other
negative power of ten) as a
For example, suppose you have $1.03
and you spend 42c. How much money do
you have left?
System.out.println(1.03 - .42);
The right way to solve this problem is
for monetary calculations.
BigDecimal has some caveats (please see currently accepted answer).
This is not a matter of accuracy, nor is it a matter of precision. It is a matter of meeting the expectations of humans who use base 10 for calculations instead of base 2. For example, using doubles for financial calculations does not produce answers that are “wrong” in a mathematical sense, but it can produce answers that are not what is expected in a financial sense.
Even if you round off your results at the last minute before output, you can still occasionally get a result using doubles that does not match expectations.
Using a calculator, or calculating results by hand, 1.40 * 165 = 231 exactly. However, internally using doubles, on my compiler / operating system environment, it is stored as a binary number close to 230.99999… so if you truncate the number, you get 230 instead of 231. You may reason that rounding instead of truncating would have given the desired result of 231. That is true, but rounding always involves truncation. Whatever rounding technique you use, there are still boundary conditions like this one that will round down when you expect it to round up. They are rare enough that they often will not be found through casual testing or observation. You may have to write some code to search for examples that illustrate outcomes that do not behave as expected.
Assume you want to round something to the nearest penny. So you take your final result, multiply by 100, add 0.5, truncate, then divide the result by 100 to get back to pennies. If the internal number you stored was 3.46499999…. instead of 3.465, you are going to get 3.46 instead 3.47 when you round the number to the nearest penny. But your base 10 calculations may have indicated that the answer should be 3.465 exactly, which clearly should round up to 3.47, not down to 3.46. These kinds of things happen occasionally in real life when you use doubles for financial calculations. It is rare, so it often goes unnoticed as an issue, but it happens.
If you use base 10 for your internal calculations instead of doubles, the answers are always exactly what is expected by humans, assuming no other bugs in your code.