Big Decimal

In Chapter 2, I introduced a CheckingAccount class with a balance field. I declared this field to be of type int, and included a comment stating that balance represents the number of dollars that can be withdrawn. Alternatively, I could have stated that balance represents the number of pennies that can be withdrawn.

Perhaps you are wondering why I did not declare balance to be of type double or float. That way, balance could store values such as 18.26 (18 dollars in the whole number part and 26 pennies in the fraction part). I did not declare balance to be a double or float for the following reasons:

■ Not all floating-point values that can represent monetary amounts (dollars and cents) can be stored exactly in memory. For example, 0.1 (which you might use to represent 10 cents), has no exact storage representation. If you executed double total = 0.1; for (int i = 0; i < 50; i++) total += 0.1; System.out.println(total);, you would observe 5.099999999999998 instead of the correct 5.1 as the output.

■ The result of each floating-point calculation needs to be rounded to the nearest cent. Failure to do so introduces tiny errors that can cause the final result to differ from the correct result. Although Math supplies a pair of round() methods that you might consider using to round a calculation to the nearest cent, these methods round to the nearest integer (dollar).

Listing 6-3's InvoiceCalc application demonstrates both problems. However, the first problem is not serious because it contributes very little to the inaccuracy. The more serious problem occurs from failing to round to the nearest cent after performing a calculation.

Listing 6-3. Floating-point-based invoice calculations leading to confusing results import java.text.NumberFormat;

class InvoiceCalc {

final static double DISCOUNT_PERCENT = 0.1; // 10% final static double TAX_PERCENT = 0.05; // 5%

public static void main(String[] args) {

double invoiceSubtotal = 285.36;

double discount = invoiceSubtotal*DISCOUNT_PERCENT;

double subtotalBeforeTax = invoiceSubtotal-discount;

double salesTax = subtotalBeforeTax*TAX_PERCENT;

double invoiceTotal = subtotalBeforeTax+salesTax;

NumberFormat currencyFormat = NumberFormat.getCurrencyInstance();

System.out.println("Subtotal: " + currencyFormat.format(invoiceSubtotal));

System.out.println("Discount: " + currencyFormat.format(discount));

System.out.println("SubTotal after discount: " +

currencyFormat.format(subtotalBeforeTax)); System.out.println("Sales Tax: " + currencyFormat.format(salesTax)); System.out.println("Total: " + currencyFormat.format(invoiceTotal));

Listing 6-3 relies on the NumberFormat class (located in the java.text) package and its format() method to format a double precision floating-point value into a currency—I will discuss NumberFormat in Chapter 9. When you run InvoiceCalc, you will discover the following output:

Subtotal: $285.36

Discount: $28.54

SubTotal after discount: $256.82

Sales Tax: $12.84

Total: $269.67

This output reveals the correct subtotal, discount, subtotal after discount, and sales tax. In contrast, it incorrectly reveals 269.67 instead of 269.66 as the final total. The customer will not appreciate paying an extra penny, even though 269.67 is the correct value according to the floating-point calculations:

Subtotal: 285.36 Discount: 28.536

SubTotal after discount: 256.824 Sales Tax: 12.8412 Total: 269.6652

The problem arises from not rounding the result of each calculation to the nearest cent before performing the next calculation. As a result, the 0.024 in 256.824 and 0.0012 in 12.84 contribute to the final value, causing NumberFormat's format() method to round this value to 269.67.

Java provides a solution to both problems in the form of a java.math.BigDecimal class. This immutable class (a BigDecimal instance cannot be modified) represents a signed decimal number (such as 23.653) of arbitrary precision (number of digits) with an associated scale (an integer that specifies the number of digits after the decimal point).

BigDecimal declares three convenience constants: ONE, TEN, and ZERO. Each constant is the BigDecimal equivalent of 1, 10, and 0 with a zero scale.

CAUTION: BigDecimal declares several ROUND_-prefixed constants. These constants are largely obsolete and should be avoided, along with the public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) and public BigDecimal setScale(int newScale, int roundingMode) methods, which are still present so that dependent legacy code continues to compile.

BigDecimal also declares a variety of useful constructors and methods. A few of these constructors and methods are described in Table 6-2.

Table 6-2. BigDecimal Constructors and Methods

Method

+1 0

Post a comment