public class DoubleForCurrency {
public static void main(String[] args) {
double total = 0.2;
for (int i = 0; i < 100; i++) {
total += 0.2;
}
System.out.println("total = " + total);
}
}
Which data type would you choose for storing Monetary Values, Currency in Java
Upasana | August 10, 2019 | 5 min read | 1 views
Float & Double are bad for financial (even for military use) world, never use them for monetary calculations. If precision is one of your requirements, use BigDecimal
instead.
Lets see the problem with the help of this example:
All floating point values that can represent a currency amount (in dollars and cents) can not be stored exactly as it is in the memory. So if we want to store 0.1 dollar (10 cents), float/double can not store it as it is, instead binary can store only a closer approximation value (0.100000001490116119384765625 in decimal). The magnitude of this problem becomes significant (known as loss of significance) when we repetitively do arithmetic operations (multiply or divide) using these two data types. Let’s try to understand this fact by taking this simple example
total = 20.19999999999996
The output should have been 20.20
(20 dollars and 20 cents), but floating point calculation made it 20.19999999999996
. That’s loss of precision (or loss of significance)
Real cause of loss of significance
Whether or not a rational number has a terminating expansion depends on the base. For example, in base-10 the number 1/2 has a terminating expansion (0.5) while the number 1/3 does not (0.333…). In base-2 only rationals with denominators that are powers of 2 (such as 1/2 or 3/16) are terminating. Any rational with a denominator that has a prime factor other than 2 will have an infinite binary expansion. This means that numbers which appear to be short and exact when written in decimal format may need to be approximated when converted to binary floating-point. For example, the decimal number 0.1 is not representable in binary floating-point of any finite precision; the exact binary representation would have a "1100" sequence continuing endlessly:
e = −4; s = 1100110011001100110011001100110011…, where, as previously, s is the significand and e is the exponent.
When rounded to 24 bits this becomes
e = −4; s = 110011001100110011001101, which is actually 0.100000001490116119384765625 in decimal.
BigDecimal for the rescue
BigDecimal represents a signed decimal number of arbitrary precision with an associated scale. BigDecimal provides full control over the precision and rounding of the number value. Virtually its possible to calculate value of pi to 2 billion decimal places using BigDecimal, available physical memory being the only limit.
That’s the reason we should always prefer BigDecimal or BigInteger for financial calculations.
Primitive type - int and long are also useful for monetary calculations if decimal precision is not required.
We should really avoid using BigDecimal(double value) constructor instead prefer BigDecimal(String) because BigDecimal (0.1) results in (0.1000000000000000055511151231257827021181583404541015625) being stored in BigDecimal instance. In contrast BigDecimal ("0.1") stores exactly 0.1
Precision is the total number of digits (or significant digits) of a real number
Scale specifies number of digits after decimal place For example, 12.345 has precision of 5 (total digits) and scale of 3 (number of digits right of the decimal)
How to format BigDecimal Value without getting exponentiation in the result & Strip the trailing zeros?
We might get exponentiation in the calculation result if we do not follow some best practices while using BigDecimal
. Below is the code snippet which shows a good usage example of handling the calculation result using BigDecimal
.
import java.math.BigDecimal;
public class BigDecimalForCurrency {
public static void main(String[] args) {
int scale = 4;
double value = 0.11111;
BigDecimal tempBig = new BigDecimal(Double.toString(value));
tempBig = tempBig.setScale(scale, BigDecimal.ROUND_HALF_EVEN);
String strValue = tempBig.stripTrailingZeros().toPlainString();
System.out.println("tempBig = " + strValue);
}
}
tempBig = 0.1111
NumberFormat
class is designed specifically for this purpose. Currency symbol & Rounding Mode is automatically set based on the locale using NumberFormat
. Lets see this in action
class Scratch {
public static String formatRupees(double value) {
NumberFormat format = NumberFormat.getCurrencyInstance(new Locale("en", "in"));
format.setMinimumFractionDigits(2);
format.setMaximumFractionDigits(5);
format.setRoundingMode(RoundingMode.HALF_EVEN);
return format.format(value);
}
public static void main(String[] args) {
BigDecimal tempBig = new BigDecimal(22.121455);
System.out.println("tempBig = " + formatRupees(tempBig.doubleValue()));
}
}
tempBig = Rs.22.12146
That’s all, everything is taken care by NumberFormat.
Some precautions
-
BigDecimal(String) constructor should always be preferred over BigDecimal(Double), because using
BigDecimal(double)
is unpredictable due to inability of the double to represent 0.1 as exact 0.1 -
If double must be used for initializing a BigDecimal, use
BigDecimal.valueOf(double)
which converts Double value to string using Double.toString(double) method -
Rounding mode should be provided while setting the scale
-
StripTrailingZeros chops off all the trailing zeros
-
toString() may use scientific notation but, toPlainString() will never return exponentiation in its result
Do you know?
On February 25, 1991, a loss of significance in a MIM-104 Patriot missile battery prevented it from intercepting an incoming Scud missile in Dhahran, Saudi Arabia, contributing to the death of 28 soldiers from the U.S. Army’s 14th Quartermaster Detachment - http://www.gao.gov/products/IMTEC-92-26
Since the introduction of IEEE 754, the default method (round to nearest, ties to even, sometimes called Banker’s Rounding or RoundingMode.HALF_EVEN
) is more commonly used USA. This method rounds the ideal (infinitely precise) result of an arithmetic operation to the nearest representable value, and gives that representation as the result. In the case of a tie, the value that would make the significand end in an even digit is chosen.
For further reading -
-
https://blogs.oracle.com/CoreJavaTechTips/entry/the_need_for_bigdecimal
-
Item 60, Effective Java 3rd Edition by Joshua Bloch
Top articles in this category:
- Multi-threading Java Interview Questions for Investment Bank
- Cracking core java interviews - question bank
- ION Trading Java Interview Questions
- Hibernate & Spring Data JPA interview questions
- Difference between getOne and findById in Spring Data JPA?
- Goldman Sachs Java Interview Questions
- Java Concurrency Interview Questions