Home » Java » Core Java » Compounding double error

About Peter Lawrey

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 our best selling eBooks for FREE!

1. JPA Mini Book

2. JVM Troubleshooting Guide

3. JUnit Tutorial for Unit Testing

4. Java Annotations Tutorial

5. Java Interview Questions

6. Spring Interview Questions

7. Android UI Design

and many more ....

 

Leave a Reply

Your email address will not be published. Required fields are marked *

*


5 + = six

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Want to take your Java Skills to the next level?
Grab our programming books for FREE!
  • Save time by leveraging our field-tested solutions to common problems.
  • The books cover a wide range of topics, from JPA and JUnit, to JMeter and Android.
  • Each book comes as a standalone guide (with source code provided), so that you use it as reference.
Last Step ...

Where should we send the free eBooks?

Good Work!
To download the books, please verify your email address by following the instructions found on the email we just sent you.