6

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.

lostinmoney
  • 256
  • 1
  • 11

6 Answers6

8

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.

Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
  • Many thanks. Then which one is your suggestion? 1. Use BigDecimal for all fields. 2. Use long to represent these fields. – lostinmoney Dec 06 '11 at 12:56
  • `double` allows you to store up to 16 most significant digits, `long` - 18 digits starting from 0. `double` may drop least significant digits without warning, `long` on the other hand might overflow. If you really care about accuracy and reliability, always use `BigDecimal` (unless you **really** know the lower and upper range of values beforehand). After all, is it better to have fast or correct program? – Tomasz Nurkiewicz Dec 06 '11 at 13:02
  • 1
    If you're literally multiplying one dollar amount by another dollar amount, you've got bigger problems than your choice of data-type . . . – ruakh Dec 06 '11 at 13:09
  • Then the only purpose to use long is performance? Can I come to a conclusion: always use BigDecimal to represent monetary fields, try to use long if there is any performance issue? Is it acceptable to use double in high frequency trading after all double is quicker many times than BigDecimal? – lostinmoney Dec 06 '11 at 13:23
  • @lostinmoney, most trading systems use double, long or int for prices and quantities (which are not quite the same as money) – Peter Lawrey Dec 06 '11 at 13:25
  • @Peter, many thanks for your comments. Yes, I saw lots of trading system are using double to represent price, quantity, and even amount, but using BigDecimal to take care of the arithmetic. The only benefit for this I can see is saving some space. Is there any convention for this kind of usage in trading system? – lostinmoney Dec 06 '11 at 13:36
  • 1
    @lostinmoney, The saving for double are memory, cpu and code size. The benefit for BigDecimal is less error prone and more predictable rounding behaviour. IMHO, It really depends on your project which is best. The convention I use with double is always round the result to the desired precision. – Peter Lawrey Dec 06 '11 at 13:39
6

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)
DuncanKinnear
  • 4,563
  • 2
  • 34
  • 65
3

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.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
2

long will be much better choice than double/float.

Are you sure that using BigDecimal type will be a real bottleneck?

Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
Artem
  • 4,347
  • 2
  • 22
  • 22
  • Since this is not a simple monetary application which only deals with cents. There may be some price/amount that we need to keep at least 5 decimal points. – lostinmoney Dec 06 '11 at 12:49
  • +1. But to clarify: if you use `int`s or `long`s, you'll have to use some sort of constant factor, usually 100 or 1000, to support less-than-one-monetary-unit. For example, you might represent $1 as 1000L, and 1¢ as 10L. And it's a good idea to wrap this in its own data-type. – ruakh Dec 06 '11 at 12:50
0

If the only use of double is to store decimal values, then yes, you can under some conditions: if you can guarantee that your values have no more than 15 decimal digits, then converting a value to double (53 bits of precision) and converting the double back to decimal with 15-digit precision (or less) will give you the original value, i.e. without any loss, from an application of David Matula's theorem proved in his article In-and-out conversions. Note that for this result to be applicable, the conversions must be done with correct rounding.

Note however that a double may not be the best choice: monetary values are generally expressed not in floating point, but in fixed point with a few digits (p) after the decimal point, and in this case, converting the value to an integer with a scaling by 10^p and storing this integer (as others suggested) is better.

vinc17
  • 2,829
  • 17
  • 23
0

Pit fall is that floats/doubles can not store all values without losing precision. Even if you do your use BigDecimal and preserve precision during calculations, you are still storing the end product as a float/double.

The "proper" solution to this, in my experience, is to store monetary values as integers (e.g. Long) representing thousands of a dollar. This gives sufficient resolution for most tasks, e.g. interest accruement, while side stepping the problem of using floats/doubles. As an added "bonus", this requires about the same amount of storage as floats/doubles.

freespace
  • 16,529
  • 4
  • 36
  • 58