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 - How to use BigDecimal arithmetic in the Invoice application (Page 4 of 4 )

Figure 4 shows how you can use BigDecimal arithmetic in the Invoice application. To start, look at the console output when BigDecimal is used. As you can see, this solves both the rounding problem and the floating-point problem so it now works the way you want it to.

To use BigDecimal arithmetic in the Invoice application, you start by coding an import statement that imports all of the classes and enumerations of the java.math package. This includes both the BigDecimal class and the RoundingMode enumeration. Then, you use the constructors and methods of the BigDecimal class to create the BigDecimal objects, do the calculations, and round the results when necessary.

In this figure, the code starts by constructing BigDecimal objects from the subtotal and discountPercent variables, which are double types. To avoid conversion problems, though, the toString method of the Double class is used to convert the subtotal and discountPercent values to strings that are used in the BigDecimal constructors.

Since the user may enter subtotal values that contain more than two decimal places, the setScale method is used to round the subtotal entry after it has been converted to a BigDecimal object. However, since the discountPercent variable only contains two decimal places, it isn’t rounded. From this point on, all of the numbers are stored as BigDecimal objects and all of the calculations are done with BigDecimal methods.

In the statements that follow, only discount amount and sales tax need to be rounded. That’s because they’re calculated using multiplication, which can result in extra decimal places. In contrast, the other numbers (total before tax and total) don’t need to be rounded because they’re calculated using subtraction and addition. Once the calculations and rounding are done, you can safely use the NumberFormat objects and methods to format the BigDecimal objects for display.

When working with BigDecimal objects, you may sometimes need to create one BigDecimal object from another BigDecimal object. However, you can’t supply a BigDecimal object to the constructor of the BigDecimal class. Instead, you need to call the toString method from the BigDecimal object to convert the BigDecimal object to a String object. Then, you can pass that String object as the argument of the constructor as illustrated by the last statement in this figure.

Is this a lot of work just to do simple business arithmetic? Relative to some other languages, you would have to say that it is. In fact, it’s fair to say that this is a weakness of Java when it is compared to languages that provide a decimal data type. Once you get the hang of working with the BigDecimal class, though, you should be able to solve all of your floating-point and rounding problems with relative ease.

The Invoice application output when BigDecimal arithmetic is used

Figure 4. How to use BigDecimal arithmetic in the Invoice application

The import statement that’s required for BigDecimal arithmetic

import java.math.*; // imports all classes and enumerations in java.math

The code for using BigDecimal arithmetic in the Invoice application

// convert subtotal and discount percent to BigDecimal BigDecimal decimalSubtotal = new BigDecimal(Double.toString(subtotal)); decimalSubtotal = decimalSubtotal.setScale(2, RoundingMode.HALF_UP); BigDecimal decimalDiscountPercent = new BigDecimal(Double.toString(discountPercent)); // calculate discount amount BigDecimal discountAmount = decimalSubtotal.multiply(decimalDiscountPercent); discountAmount = discountAmount.setScale(2, RoundingMode.HALF_UP); // calculate total before tax, sales tax, and total BigDecimal totalBeforeTax = decimalSubtotal.subtract(discountAmount); BigDecimal salesTaxPercent = new BigDecimal(".05"); BigDecimal salesTax = salesTaxPercent.multiply(totalBeforeTax); salesTax = salesTax.setScale(2, RoundingMode.HALF_UP); BigDecimal total = totalBeforeTax.add(salesTax);

How to create a BigDecimal object from another BigDecimal object

BigDecimal total2 = new BigDecimal(total.toString());

Description

With this code, all of the result values are stored in BigDecimal objects, and all of the results have two decimal places that have been rounded correctly when needed.

Once the results have been calculated, you can use the NumberFormat methods to format the values in the BigDecimal objects without any fear of rounding problems. However, the methods of the NumberFormat object limits the results to 16 significant digits.

Summary

If you haven’t used the BigDecimal class before, I hope this article has demonstrated the need for it and will get you started using it. Also, if you like this article and our “paired pages” presentation method, I hope it will encourage you to review our Java 5 book or some of our other books.

Unlike many Java books, Murach’s Beginning Java 2, JDK 5is designed to teach you the essential skills for developing business applications. That’s why it shows you how to use the BigDecimal class, which isn’t even mentioned in many competing books. That’s also why it shows you how to do data validation at a professional level and how to develop three-tier database applications, two more essentials that are commonly omitted or neglected in competing books.

DISCLAIMER: The content provided in this article is not warranted or guaranteed by Developer Shed, Inc. The content provided is intended for entertainment and/or educational purposes in order to introduce to the reader key ideas, concepts, and/or product reviews. As such it is incumbent upon the reader to employ real-world tactics for security and implementation of best practices. We are not liable for any negative consequences that may result from implementing any information covered in our articles or tutorials. If this is a hardware review, it is not recommended to open and/or modify your hardware.