How the BigDecimal Class Helps Java Get its Arithmetic Right

If you use Java for simple business arithmetic, you might be seeing some errors. It's not your fault, it's a floating-point problem -- and this article explains how to use Java's BigDecimal class to fix it. It is excerpted from the book Murach's Beginning Java 2, JDK 5, written by Doug Lowe, Joel Murach, and Andrea Steelman (Murach Publishing, 2005; ISBN: 1890774294).

How the BigDecimal Class Helps Java Get its Arithmetic Right - The math problems in the Invoice application (Page 2 of 4 )

The console at the top of figure 2 shows more output from the Invoice application in figure 1. But wait! The results for a subtotal entry of 100.05 don’t add up. If the discount amount is $10.00, the total before tax should be $90.05, but it’s $90.04. Similarly, the sales tax for a subtotal entry of .70 is shown as $0.03, so the invoice total should be $0.73, but it’s shown as is $0.74. What’s going on?

To analyze data problems like this, you can add debugging statements like the ones in this figure. These statements display the unformatted values of the result fields so you can see what they are before they’re formatted and rounded. This is illustrated by the console at the bottom of this figure, which shows the results for the same entries as the ones in the console at the top of this figure.

If you look at the unformatted results for the first entry (100.05), you can easily see what’s going on. Because of the way NumberFormat rounding works, the discount amount value of 10.005 and the total before tax value of 90.045 aren’t rounded up. However, the invoice total value of 94.54725 is rounded up. With this extra information, you know that everything is working the way it’s supposed to, even though you’re not getting the results you want.

Now, if you look at the unformatted results for the second entry (.70), you can see another type of data problem. In this case, the sales tax is shown as .034999999999999996 when it should be .035. This happens because floating-point numbers aren’t able to exactly represent some decimal fractions. As a result, the formatted value is $0.03 when it should be rounded up to $0.04. However, the unformatted invoice total is correctly represented as 0.735, which is rounded to a formatted $0.74. And here again, it looks like Java can’t add.

Although trivial errors like these are acceptable in many applications, they are unacceptable in most business applications. And for those applications, you need to provide solutions that deliver the results that you want. (Imagine getting an invoice that didn’t add up!)

One solution is to write your own code that does the rounding so you don’t need to use the NumberFormat class to do the rounding for you. However, that still doesn’t deal with the fact that some decimal fractions can’t be accurately represented by floating-point numbers. To solve that problem as well as the other data problems, the best solution is to use the BigDecimal class that you’ll learn about next.

Figure 2The math problems in the Invoice application

Output data that illustrates a problem with the Invoice application

Statements that you can add to the program to help analyze this problem

// debugging statements that display the unformatted fields // these are added before displaying the formatted results String debugMessage = "\nUNFORMATTED RESULTS\n" + "Discount percent: " + discountPercent + "\n" + "Discount amount: " + discountAmount + "\n" + "Total before tax: " + totalBeforeTax + "\n" + "Sales tax: " + salesTax + "\n" + "Invoice total: " + total + "\n" + "\nFORMATTED RESULTS"; System.out.println(debugMessage);