Many applications deal with currency and it is frequently an integral component of business applications. There are many issues that complicate the representation and use of currency values such as precision and accuracy. In addition, there are differences between how currency types such as rubles and the yen are displayed. In this recipe, we will examine the data types we can use to represent currency and locale-specific issues regarding currency.
There are several potential data types that can be used for representing currency including:
BigDecimal
To determine which is best we need to consider issues such as precision and accuracy. Ease of use is another concern since we will need to be able to manipulate currency values. We also need to consider the locale to insure the currency we are using is expressed in a locale-specific format.
Floating points are a poor choice for representing currency. Their major drawback is their lack of precision. To understand this limitation, consider how we represent the fraction 1/3 as a decimal number: 0.33333. We cannot precisely represent this fraction as a decimal number since it would take an infinite number of trailing 3s. The fraction 2/3 has a similar problem: 0.66666. Adding 1/3 plus 2/3 gives 1. However, adding 0.33333 plus 0.66666 gives 0.99999. Close, but it does not provide sufficient precision. While we can round the result, floating point numbers do not provide us with much control over the rounding process. Floating point numbers are not exact while currency values require exactness.
Integers could be used but it requires the developer to explicitly keep track of the decimal point. Assuming this is not too burdensome, which it probably is in operations like multiplication, we still need to display the results of our calculations. This will require us to extract the last two digits (assuming cents and dollars is our currency) and convert the result to a properly formatted string. This approach has not been widely used due to the lack of support.
The BigDecimal
class is a member of the java.math
package. It supports arbitrary precision decimal numbers and standard arithmetic operations. One drawback to its use is each BigDecimal
object is immutable which can result in inefficiencies associated with the creation and garbage collection of objects. However, it is the preferred approach.
A BigDecimal
number can be created using a number of overloaded constructors. One approach is to use a single double
number as the argument of the constructor. Here, 1045.32 is used to create a BigDecimal
object. The value is displayed without formatting and the scale
method is used to determine the number of digits to the right of the decimal point.
BigDecimal amount; amount = amount.add(new BigDecimal(1045.32)); System.out.println("amount Unformatted: " + amount.toString()); System.out.println("Scale of amount: " + amount.scale());
The console output appears as follows:
INFO: amount Unformatted: 1045.319999999999936335370875895023345947265625
INFO: Scale of amount: 42
Contrast this with creating a BigDecimal
number using a string.
BigDecimal amount2; amount2= new BigDecimal("1045.32"); System.out.println("amount2 Unformatted: " + amount2.toString()); System.out.println("Scale of amount: " + amount2.scale());
The output follows:
INFO: amount2 Unformatted: 1045.32
INFO: Scale of amount: 2
When formatting a BigDecimal
, we can use the java.text.NumberFormat
class. Its getCurrencyInstance
method returns a NumberFormat
object formatted according to a specific locale. Below, we use the Locale.US
with the amount2
variable:
NumberFormat numberFormat = NumberFormat.getCurrencyInstance(Locale.US); System.out.println("amount2 formatted: " + numberFormat.format(amount2));
The output will appear as follows:
INFO: amount2 formatted: $1,045.32
Basic arithmetic operations are supported by BigDecimal
. For example, to add two numbers we can use the add
method.
BigDecimal number1 = new BigDecimal("32.54"); BigDecimal number2 = new BigDecimal("8.44"); number1 = number1.add(number2); System.out.println(numberFormat.format(number1));
This results in the following console output:
INFO: $40.98
BigDecimal
supports other operations including subtraction, multiplication, division, and exponentiation.
The first set of code examples illustrated the use of a string to construct a BigDecimal
object. This approach simplified the setting of its scale. The scale is determined by the number contained in the string. A number represented as a string with three digits after the decimal point would have a scale of 3. Using a string is the preferred way of converting a floating point value to a BigDecimal
. The use of a constructor using a float
or double
value can sometimes be unpredictable.
The third example illustrated the use of the NumberFormat
class to format the appearance of a number. The getCurrencyInstance
method used a Local
value to format the value based on a particular local.
The fourth example demonstrated how to perform arithmetic operations on BigDecimal
objects. This is further elaborated in the next section.
The BigDecimal
class is a useful tool for dealing with currency. However, we need to be careful in a few areas:
BigDecimal
objects BigDecimal
objectsIn the previous example, two numbers were added together and assigned to the first number.
BigDecimal number1 = new BigDecimal("32.54"); BigDecimal number2 = new BigDecimal("8.44"); number1 = number1.add(number2);
This is the correct way to add the numbers. However, do not use the following approach and expect number1
to be modified:
number1.add(number2);
Remember, BigDecimal
objects are immutable. This operation modifies neither number1
nor number2
but returns a new BigDecimal
object containing their sum.
A common requirement is to keep a cumulative sum. The following code illustrates the essential elements of this technique:
BigDecimal total = BigDecimal.ZERO; for(...) { total = total.add(numbers[i]); }
Notice the use of BigDecimal.ZERO
. This constant represents a zero and is used to initialize total
. The value returned by numbers[i]
is added with total
and the residual value is assigned to total
. There are two other constants available: BigDecimal.ONE
and BigDecimal.TEN
representing the values of 1 and 10 respectively.
When comparing two BigDecimal
numbers, we have to be careful when using the equals
method. Normally, this is the preferred method for comparing two objects as the equality operator simply compares reference values.
The BigDecimal's equals
method bases its comparison on the values of the number and the scale used. Consider the following example. Two BigDecimal
numbers are assigned the same number except for the number of digits to the right of the decimal point. The equals
method returns false
. However, the compareTo
method works properly.
number1 = new BigDecimal("1.00"); number2 = new BigDecimal("1.000"); System.out.println(number1.equals(number2)); System.out.println(number1.compareTo(number2)==0); System.out.println();
The output:
INFO: false
INFO: true
The equals
method uses the number and the scale to determine equality. Since the two numbers have a different scale, they are not considered to be equal. The compareTo
method returns a negative value if number1
is less than number2
, a zero if they are equal and a positive number if number1
is greater than number2
.
Rounding can be an important part of a computation. BigDecimal
provides several rounding options. When rounding should be applied is dependent upon the nature of the calculation. Consider the addition of 0.134 and 0.133. If we add the numbers together and then round to two decimal places we get 0.27. However, if we round the two numbers first to 0.13 then add them together their sum is 0.26. The right approach is dependent on the problem we are trying to solve.
Rounding is supported using the round
method. This method takes a java.math.MathContext
object. The two argument constructor used below takes the precision as its first argument and a rounding method as its second argument. The rounding options are similar to those used with BigDecimal
as explained shortly.
In the following example, we implement the addition problem discussed previously. The variables, number1
and number2
, are added together and then rounded. The numbers, number3
and number4
corresponding to the first two numbers, are rounded and then added. All rounding uses a precision of two digits.
number1 = new BigDecimal("0.0134"); number2 = new BigDecimal("0.0133"); BigDecimal number3 = number1.round(new MathContext(2,RoundingMode.HALF_UP)); BigDecimal number4 = number2.round(new MathContext(2,RoundingMode.HALF_UP)); System.out.println(number1.add(number2).round(new MathContext(2,RoundingMode.HALF_UP))); System.out.println(number3.add(number4).round(new MathContext(2,RoundingMode.HALF_UP))); System.out.println();
The output below confirms the differences in the addition using the two rounding approaches.
INFO: 0.027
INFO: 0.026
The BigDecimal
class provides eight rounding techniques. If the rounding mode is not specified, it uses the most appropriate scale and rounding based on the operation performed. While not detailed here, the BigDecimal.ROUND_HALF_UP
will round up if the fraction part is greater than or equal to 0.5. However, BigDecimal.ROUND_HALF_EVEN
best minimizes errors that can accumulate over a series of calculations.
3.145.96.86