Compounding double error

Overview

In a previous article, I outlined why BigDecimal is not the answer most of the time. While it is possible to construct situations where double produces an error, it is also just as easy to construct situations were BigDecimal get an error.

BigDecimal is easier to get right, but easier to get wrong.

The anecdotal evidence is that junior developers don’t have as much trouble getting BigDecimal right as they do getting double with rounding right.  However, I am sceptical of this because in BigDecimal it is much easier for an error to go unnoticed as well.

Lets take this example where double produces an incorrect answer.

double d = 1.00;
d /= 49;
d *= 49 * 2;
System.out.println("d=" + d);

BigDecimal bd = BigDecimal.ONE;
bd = bd .divide(BigDecimal.valueOf(49), 2, BigDecimal.ROUND_HALF_UP);
bd = bd.multiply(BigDecimal.valueOf(49*2));
System.out.println("bd=" + bd);

prints

d=1.9999999999999998
bd=1.96

In this case, double looks wrong, it needs rounding which would give the correct answer of 2.0. However the BigDecimal looks right, but it isn’t due to representation error. We could change the division to use more precision, but you will always get a representation error, though you can control how small that error is.

You have to ensure numbers are real and use rounding.

Even with BigDecimal, you have to use appropriate rounding. Lets say you have a loan for $1,000,000 and you apply 0.0005% interest per day. The account can only have a whole number of cents, so rounding is needed to make this a real amount of money. If don’t do this how long does it take to make a 1 cent difference?

double interest = 0.0005;
BigDecimal interestBD = BigDecimal.valueOf(interest);

double amount = 1e6;
BigDecimal amountBD = BigDecimal.valueOf(amount);
BigDecimal amountBD2 = BigDecimal.valueOf(amount);

long i = 0;
do {
    System.out.printf("%,d: BigDecimal: $%s, BigDecimal: $%s%n", i, amountBD, amountBD2);
     i++;
    amountBD = amountBD.add(amountBD.multiply(interestBD)
                       .setScale(2, BigDecimal.ROUND_HALF_UP));
    amountBD2 = amountBD2.add(amountBD2.multiply(interestBD));

} while (amountBD2.subtract(amountBD).abs()
                 .compareTo(BigDecimal.valueOf(0.01)) < 0);
System.out.printf("After %,d iterations the error was 1 cent and you owe %s%n", i, amountBD);

prints finally

8: BigDecimal: $1004007.00, 
   BigDecimal: $1004007.00700437675043756250390625000000000000000
After 9 iterations the error was 1 cent and you owe 1004509.00

You could round the result but this hide the fact you are off by a cent even though you used BigDecimal.

double eventually has a representation error

Even if you use appropriate rounding, double will give you an incorrect result. It is much later than the previous example.

 double interest = 0.0005;
BigDecimal interestBD = BigDecimal.valueOf(interest);
double amount = 1e6;
BigDecimal amountBD = BigDecimal.valueOf(amount);
long i = 0;
do {
    System.out.printf("%,d: double: $%.2f, BigDecimal: $%s%n", i, amount, amountBD);
    i++;
    amount = round2(amount + amount * interest);
    amountBD = amountBD.add(amountBD.multiply(interestBD)
                       .setScale(2, BigDecimal.ROUND_HALF_UP));
} while (BigDecimal.valueOf(amount).subtract(amountBD).abs()
                   .compareTo(BigDecimal.valueOf(0.01)) < 0);
System.out.printf("After %,d iterations the error was 1 cent and you owe %s%n", i, amountBD);

prints finally

22,473: double: $75636308370.01, BigDecimal: $75636308370.01
After 22,474 iterations the error was 1 cent and you owe 75674126524.20

From an IT perspective we have an error of one cent, from a business perspective we have a client who has made no repayments for more than 9 years and owes the bank $75.6 billion, enough to bring down the bank. If only the IT guy had used BigDecimal!?

Conclusion

My final recommendation is that you should use what you feel comfortable with, don't forget about rounding, do use real numbers, not whatever the mathematics produces e.g. can I have fractions of a cent, or can I trade fractions of share.  Don't forget about the business perspective. You might find that BigDecimal makes more sense for your company, your project or your team.

Don't assume BigDecimal is the only way, don't assume the problems double faces don't apply also to BigDecimal. BigDecimal is not a ticket to best practice coding, because complacency is a sure way of introducing errors.

Reference: Compounding double error from our JCG partner Peter Lawrey at the Vanilla Java blog.

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

JPA Mini Book

Learn how to leverage the power of JPA in order to create robust and flexible Java applications. With this Mini Book, you will get introduced to JPA and smoothly transition to more advanced concepts.

JVM Troubleshooting Guide

The Java virtual machine is really the foundation of any Java EE platform. Learn how to master it with this advanced guide!

Given email address is already subscribed, thank you!
Oops. Something went wrong. Please try again later.
Please provide a valid email address.
Thank you, your sign-up request was successful! Please check your e-mail inbox.
Please complete the CAPTCHA.
Please fill in the required fields.

Leave a Reply


five × 6 =



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.
Do you want to know how to develop your skillset and become a ...
Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

Get ready to Rock!
You can download the complementary eBooks using the links below:
Close