Monetary Calculation Pitfalls

People expect computer programs to be accurate and precise when calculating numbers, and especially programs that perform monetary calculations and as a Java developer, sooner or later you will need to deal with monetary calculations.

You may be tempted to use the primitive types of float or double for non-integer numbers. Both of these types support numbers with decimals. As we will see below, there are some significant pitfalls in using these primitive types for monetary calculations.

Monetary Calculations: Bad Examples

You have 1.55 $ and you bought a candy worth 35 ¢. How much is left with you?

Let’s do the calculation in Java.

If you said 1.20 $, you are in for a surprise. The output is:

Let us look at another example, this time using double. A product is priced 10 $. What is the price after adding 8.25 % tax to it?

The output is:

We again have some unexpected numbers. A simple multiplication “10.00 X .0825” is not giving us what we expected “0.825”.

So what’s the problem? Why can’t Java perform such simple calculations? The problem is not with Java but how floating point is defined in the IEEE 754 standard based on which Java implements float and double. Floating points are designed to provide accurate approximations (but not exact results) quickly and it is impossible to exactly represent 0.1 (or any other negative power of ten) as a Java float or double.

Without going into more details of binary floating point arithmetic, let’s find out how to perform monetary calculations accurately in Java. A solution is to do everything using integral types (int and long) and I have met several programmers advocating it. But then you will need to remember that “325” in your program is really “3.25” dollar. Also, how will you do percent calculations rounding to the nearest cent? This is when you should turn to the BigDecimal class.

Monetary Calculations Using BigDecimal

The BigDecimal class is a part of the java.math package. For a decimal number, BigDecimal internally stores the unscaled value in a BigInteger and the decimal scale (digits to the right of the decimal point) in an integer. So, the internally used BigInteger allows BigDecimal to represent any number, however big it is (only limited to physical memory) and the integer allows accurate handling of the decimal scale.

During addition and subtraction operations, BigDecimal expands the number with the smallest scale before performing the operation. This guarantees that the sum or difference is exact to the last digit. During multiplication, BigDecimal computes the sum of the numbers scale and based on it expands its decimal scale. For division, BigDecimal expects that the result can be represented with a scale that is the difference between the scale of the dividend and divisor.

To perform the arithmetic calculations, BigDecimal provides the add(), subtract(), multiply(), and divide() methods. Before we use these methods, we need to represent the numbers as BigDecimal objects. The BigDecimal class contains 16 overloaded constructors but the one that you will use to represent a monetary value is BigDecimal(String val). This is important because if you mistakenly use the one that accepts double, you will face same issues as you do while using float and double. This happens because the float or double parameter value will undergo loss of precision before you pass them to the constructor. On the other hand, when you use the String constructor, BigDecimal will represent exactly the number you pass to it.

Let’s now perform some BigDecimal calculations.

In the example above, we created two BigDecimal numbers and called the the add(), subtract(), multiply(), and divide() methods to perform arithmetic calculations.

The output is:

As we can see the addition, subtraction, and multiplication in lines 4-6 were done as expected but the division in line 7 resulted in an exception. This happened because what we have is a non-terminating decimal expansion “2.5/3.5 = 0.7142857142857. . . . .”. As mentioned earlier, for division BigDecimal expects that the result can be represented with a scale that is the difference between the scale of the dividend and divisor. Else, the JavaDoc says “. . .if the exact quotient cannot be represented (because it has a non-terminating decimal expansion) an ArithmeticException is thrown”– and hence the exception thrown in line 7.

To avoid such exceptions, always set a resulting scale and a rounding mode during division by using the overloaded divide(BigDecimal divisor, int scale, RoundingMode roundingMode) method. In this method, the first parameter is the BigDecimal divisor. The second parameter specifies the decimal scale and the third an enumeration value of the RoundingMode enum. This enum, introduced in Java SE 6, provides eight different types of rounding modes whose descriptions with examples are available here.

Note: BigDecimal itself have integer fields to represent rounding modes, but they are now obsolete. Ensure that you instead use the RoundingMode enum for rounding modes in calculations.

To display BigDecimal numbers, you can set the scale and rounding mode with the BigDecimal.setScale(scale, roundingMode) method.

Now, the question is- Which rounding mode should I use for monetary calculationd? The answer is, there is “no specific” mode. It depends on the application requirements and any legal contracts that your application must conform with. For example, it is common to use RoundingMode.HALF_UP for tax calculations. If you are selling a product and want to round in favor of the customer, use RoundingMode.CEILING. If you are not sure, go for RoundingMode.HALF_EVEN. This rounding mode, also known as “Banker’s rounding”, when applied repeatedly over a sequence of calculations statistically minimizes cumulative error.

Let’s now write some code to perform some monetary calculations using BigDecimal.

In the example above, we first wrote a calculate() method that accepts two String parameters. In lines 13-14 we converted them into BigDecimal. In lines 16-18 we performed addition, subtraction, and multiplication operations on the numbers. Next, we wrote a divideWithScaleRounding() method that also accepts two String parameters that we converted to BigDecimal in line 26-27. In line 29, we performed a division with a scale 4 and a rounding mode, RoundingMode.HALF_EVEN. Then, we wrote a calculateTax() method that accepts a monetary amount and a tax as String objects. After converting the parameters to BigDecimal, we calculated the tax amount in line 36. To display the tax amount with a scale 2 and a rounding mode RoundingMode.HALF_UP, we called the setScale() method in line 38. Similarly, we calculated the final amount and set its scale and rounding mode in lines 39-40. To test our example, let’s write this unit test.

The output is:

In the output above, observe how BigDecimal performed the division and displayed monetary amounts with the specified scale and rounding mode. Using this example, go ahead and experiment with different sets of monetary values, scales, and rounding modes.

Summary

While float and double are natural choices for scientific, statistical and engineering calculations, BigDecimal is the ideal choice when it comes to monetary calculations. But, the precision and accuracy of BigDecimal comes with a price- and it is performance. However, if we require completely accurate monetary calculations, sacrifice of some performance is acceptable.
On an ending note, keep an eye on JSR 354: Money and Currency API, which is planned to be included in Java 9. A part of this API is designed to support complex monetary calculation rules including calculation and display precisions. It will be interesting to see if this API could change how we count our money in future.

Get The Code

I’ve committed the source code for this post to github. It is a Maven project which you can download and build. If you wish to learn more about the Spring Framework, I have a free introduction to Spring tutorial. You can sign up for this tutorial in the section below.

Source Code

The source code for this post is available on github. You can download it here.

Share

You May Also Like

Leave a Reply