Peter is a DZone MVB and is not an employee of DZone and has posted 157 posts at DZone. You can read more from them at their website. View Full User Profile

Double your money again

08.18.2011
| 4603 views |
  • submit to reddit

A long time ago I wrote an article on using double for money. However, it is still a common fear for many developers when the solution is fairly simple.

The problem with using double for money

double has two types of errors. It have representation error. i.e. it cannot represent all possible decimal values exactly. Even 0.1 is not exactly this value. It also has rounding error from calculations. i.e. as you perform calculations, the error increases.

double[] ds = {
        0.1,
        0.2,
        -0.3,
        0.1 + 0.2 - 0.3};
for (double d : ds) {
    System.out.println(d + " => " + new BigDecimal(d));
}
prints
0.1 => 0.1000000000000000055511151231257827021181583404541015625
0.2 => 0.200000000000000011102230246251565404236316680908203125
-0.3 => -0.299999999999999988897769753748434595763683319091796875
5.551115123125783E-17 => 5.5511151231257827021181583404541015625E-17

You can see that the representation for 0.1 and 0.2 is slightly higher than those values, and -0.3 is also slightly higher. When you print them, you get the nicer 0.1 instead of the actual value represented 0.1000000000000000055511151231257827021181583404541015625

However, when you add these values together, you get a value which is slightly higher than 0.

The important thing to remember is that these errors are not random errors. They are manageable and bounded.

Correcting for rounding error

Like many data types, such as date, you have an internal representation for a value and how you represent this as a string.

This is true for double. You need to control how the value is represented as a string. This can can be surprise as Java does a small amount of rounding for representation error is not obvious, however once you have rounding error for operations as well, it can some as a shock.

A common reaction is to assume, there is nothing you can do about it, the error is uncontrollable, unknowable and dangerous. Abandon double and use BigDecimal

However, the error is limited in the IEE-754 standards and accumulate slowly.

Round the result


And just like the need to use a TimeZone and Local for dates, you need to determine the precision of the result before converting to a String.

To resolve this issue, you need to provide appropriate rounding. With money this is easy as you know how many decimal places are appropriate and unless you have $70 trillion you won't get a rounding error large enough you cannot correct it.

// uses round half up, or bankers' rounding
public static double roundToTwoPlaces(double d) {
    return Math.round(d * 100) / 100.0;
}
// OR
public static double roundToTwoPlaces(double d) {
    return ((long) (d < 0 ? d * 100 - 0.5 : d * 100 + 0.5)) / 100.0;
}
If you add this into the result, there is still a small representation error, however it is not large enough that the Double.toString(d) cannot correct for it.
double[] ds = {
        0.1,
        0.2,
        -0.3,
        0.1 + 0.2 - 0.3};
for (double d : ds) {
    System.out.println(d + " to two places " + roundToTwoPlaces(d) + " => " + new BigDecimal(roundToTwoPlaces(d)));
}
prints
0.1 to two places 0.1 => 0.1000000000000000055511151231257827021181583404541015625
0.2 to two places 0.2 => 0.200000000000000011102230246251565404236316680908203125
-0.3 to two places -0.3 => -0.299999999999999988897769753748434595763683319091796875
5.551115123125783E-17 to two places 0.0 => 0

Conclusion

If you have a project standard which says you should use BigDecimal or double, that is what you should follow. However, there is not a good technical reason to fear using double for money.

 

From http://vanillajava.blogspot.com/2011/08/double-your-money-again.html

Published at DZone with permission of Peter Lawrey, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Scott Hickey replied on Fri, 2011/08/19 - 10:06am

You are just plain wrong to advise people that this ok.

For business applications (as opposed to scientific applications), unless there is a specific reason to store a decimal number as a Double, you should never, ever be using Doubles, especially for currency.

As soon as you use that decimal number in any kind of calculation, you are propagating an error.

In 25 years, I have yet to come across a numerical attribute on an business entity where it would be valid to represent a base 10 number imprecisely. This is NOT just a display (convert to string) problem. In accounting systems, the numbers need be exact. For financial systems, the numbers need to be exact. For sales tax calculations, the numbers need to be exact. For insurance premium calculations, .... These things all feed other systems, where one calculation will need to tie back to others.

When the business users discover these errors, the fix usually rips through whole systems. As a professional software developer, I hate being made to look like a rank amatuer when I have to explain the V.P. of Finance why 1.0 + 0.1 does not equal 1.2 in a system they paid millions of dollars for and why it will cost them more money to fix something that should never have been done that way in the first place.

Justin Forder replied on Sun, 2011/09/04 - 5:48am in response to: Scott Hickey

I remember several years ago doing some consulting for people working on a new architecture for batch calculation of stock market indices over periods of time. They were puzzled by the calculation running slower and slower as they ran it over longer periods. On investigation they found it was due to their use of BigDecimal - after running the calculation for a while the numbers they were dealing with had many thousands of digits after the point. When they explictly limited the precision the performance was fine, and they had complete control of the accuracy of the calculation. I was thinking about this recently, specifically thinking that using double would probably have been fine - so I was interested to read this article, and while my advice has always been to use BigDecimal for representing money, I can see now that it's not black and white.

As a side note, I just watched the video of Cameron Purdy's "irreverent look at the present state of Java and JDK" from the JVM Language Summit 2011; he put lack of an intrinsic decimal type (among others) at number 6 in his "Top 10 JVM erroneous zones".

Peter Lawrey replied on Fri, 2011/12/09 - 5:05am in response to: Scott Hickey

I would agree that you shouldn't use Double, Float or float.  However I have seen double used succesfully in a wide range of investment banking applications.

 

There is a common mis-conception that rounding errors are random errors which is not the case and they are mangable. (You may not believe they will be managed correctly and based on your experience that appears to be a reasonable assumption)

As you say using double correctly is more error prone and unless you know how to handle them correct and trust people to do so, then BigDecimal is a better option.

Peter Lawrey replied on Fri, 2011/12/09 - 5:13am in response to: Justin Forder

IMHO, I would say the biggest thing which makes BigDecimal error prone is the fact its not an intrinsic type. Its much harder the read and write than it needs to be or in comparison to using double.  Having seen a good number of trading system handle double simply and correctly, and given many trading system are written in languages without a decimal type, it does surprise me that people feel BigDecimal is the only solution.

 If perrformance is not an issue and correctness  is vital, e.g. accounting then BigDecimal is the only sensible option.  However if performance is a concideration and you have programmers who you can trust to round double correctly, I don't see double as being a problem.

 There are other options such as using int or long as fixed point precision in cents, or hundredth of a cent instead of dollars (or even double).  You don't get as much round errors, but some operations you have to be careful with rounding.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.