Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can we use double to store monetary fields and use BigDecimal for arithmetic

I know the problem with double/float, and it's recommended to use BigDecimal instead of double/float to represent monetary fields. But double/float is more effective and space-saving. Then my question is: It's acceptable to use double/float to represent monetary fields in Java class, but use BigDecimal to take care of the arithmetic (i.e. convert double/float to BigDecimal before any arithmetic) and equal-checking?

The reason is to save some space. And I really see lots of projects are using double/float to represent the monetary fields.

Is there any pitfall for this? Thanks in advance.

like image 847
lostinmoney Avatar asked Dec 06 '11 12:12

lostinmoney


3 Answers

No, you can't.

Suppose double is enough to store two values x and y. Then you convert them to safe BigDecimal and multiple them. The result is accurate, however if you store the multiplication result back in double, chances are you will loose the precision. Proof:

double x = 1234567891234.0;
double y = 1234567891234.0;
System.out.println(x);
System.out.println(y);

BigDecimal bigZ = new BigDecimal(x).multiply(new BigDecimal(y));
double z = bigZ.doubleValue();
System.out.println(bigZ);
System.out.println(z);

Results:

1.234567891234E12          //precise 'x'
1.234567891234E12          //precise 'y'
 1524157878065965654042756  //precise 'x * y'
1.5241578780659657E24      //loosing precision

x and y are accurate, as well as the multiplication using BigDecimal. However after casting back to double we loose least significant digits.

like image 191
Tomasz Nurkiewicz Avatar answered Nov 15 '22 07:11

Tomasz Nurkiewicz


I would also recommend that you use nothing but BigDecimal for ALL arithmetic that may involve currency.

Make sure that you always use the String constructor of BigDecimal. Why? Try the following code in a JUnit test:

assertEquals(new BigDecimal("0.01").toString(), new BigDecimal(0.01).toString());

You get the following output:

expected:<0.01[]> but was <0.01[000000000000000020816681711721685132943093776702880859375]>

The truth is, you cannot store EXACTLY 0.01 as a 'double' amount. Only BigDecimal stores the number you require EXACTLY as you want it.

And remember that BigDecimal is immutable. The following will compile:

BigDecimal amount = new BigDecimal("123.45");
BigDecimal more = new BigDecimal("12.34");
amount.add(more);
System.out.println("Amount is now: " + amount);

but the resulting output will be:

Amount is now: 123.45

That's because you need to assign the result to a new (or the same) BigDecimal variable.

In other words:

amount = amount.add(more)
like image 34
DuncanKinnear Avatar answered Nov 15 '22 07:11

DuncanKinnear


What is acceptable depends on your project. You can use double and long in some projects may be expected to do so. However in other projects, this is considered unacceptable. As a double you can represent values up to 70,000,000,000,000.00 to the cent (larger than the US national debt), with fixed place long you can represent 90,000,000,000,000,000.00 accurately.

If you have to deal with hyper-inflationary currencies (a bad idea in any case) but for some reason still need to account for every cent, use BigDecimal.

If you use double or long or BigDecimal, you must round the result. How you do this varies with each data type and BigDecimal is the least error prone as you are requires to specify what rounding and the precision for different operations. With double or long, you are left to your own devices.

like image 22
Peter Lawrey Avatar answered Nov 15 '22 06:11

Peter Lawrey