I’m studying for SCJP – currently tackling a rather dull section on regular expressions. I’ve just noticed that the exam does not cover BigDecimal. I really don’t understand this. BigDecimal is essential for any fixed precision decimal representation and calculation. Put another way: if you’re not aware of BigDecimal, you can’t effectively represent money in your program. Surely more useful than awareness of the \w, \s and \d regex metacharacters, no?
Hang on lads, I’ve got a great idea!
The standard schoolboy mistake when dealing with currency is to use the built in primitive decimal types – namely floats or doubles. They seem to do the job. They can represent pounds / euros / dollars with the digits before the decimal point and pennies / cents with the digits after the decimal point.
Don’t do that. Just don’t. You’ll get yourself into a world of pain with weird rounding problems. Check this:
float a = 1.6f; // $1.60
float b = 2.2f; // $2.20
float c = a + b; // $3.80 ?
System.out.println("$1.60 + $2.20 = $" + c);
float beer = 3.0f; // $3.00
float vat = 1.2f; // VAT = 20%
float taxedBeer = beer * vat; // $3.60?
System.out.println("$3.00 + 20% VAT = $" + taxedBeer);
Result as follows:
$1.60 + $2.20 = $3.8000002 $3.00 + 20% VAT = $3.6000001
What the hell happened there? I started using nice round numbers and ended up with rogue millionths of cents.
This happens because floats are binary approximations of the decimal constants. Base 10 numbers don’t neatly fit into base 2 bit patterns so these errors will always occur. Some good detail on the specifics here.
BigDecimal
BigDecimal is a standard J2SE class in the java.math package specifically designed for representing arbitrary precision decimal (base 10) numbers. It has methods for most common arithmetic operations and its rounding behaviour can be precisely controlled. This makes it ideal for representing currency or any precise numbers.
It works nicely with currency:
BigDecimal a = new BigDecimal("1.60");
BigDecimal b = new BigDecimal("2.20");
BigDecimal c = a.add(b);
System.out.println("$1.60 + $2.20 = $" + c);
BigDecimal beer = new BigDecimal("3.00");
BigDecimal vat = new BigDecimal("1.2");
BigDecimal taxedBeer = beer.multiply(vat);
System.out.println("$3.00 + 20% VAT = $" + taxedBeer);
Result as follows:
$1.60 + $2.20 = $3.80 $3.00 + 20% VAT = $3.600
It also gives you control of rounding when you’re doing division:
BigDecimal loot = new BigDecimal("10.00");
BigDecimal split = new BigDecimal("3");
System.out.println("$10.00 split 3 ways is $" + loot.divide(split, 2, BigDecimal.ROUND_DOWN));
System.out.println("$10.00 split 3 ways is $" + loot.divide(split, 5, BigDecimal.ROUND_UP));
Result as follows:
$10.00 split 3 ways is $3.33 $10.00 split 3 ways is $3.33334
Note: Make sure you always use the String based constructor. BigDecimal also has constructors that take a long or double but of course this brings us back to dealing with binary approximations.
Approximations and exact numbers
When deciding how to represent numbers (decimal or otherwise) we must first know if we are dealing with approximate or exact numbers. Heights, weights, temperatures and other real world measurements are approximate. You may measure your own height as 1.81m using a measuring tape. That’s accurate to 0.01m (1cm). If you use more precise measuring equipment, you may get a reading accurate to 0.001m (1mm), giving you a height of 1.812m. But no matter how precise your measuring equipment, there is always some margin of error in your measurement. The reading is an approximation.
Currency on the other hand is exact. If you have a ten pound note, it is worth exactly ten pounds. Not ten pounds plus or minus half a penny. Not even £10.0000001.
So long as you realize that there are two types of number – approximate and exact – it’s easy to pick an appropriate representation. Floats and doubles are approximations. They are accurate to several decimal places but they’re always approximations. BigDecimal is exact. It will represent exact numbers to a specified number of decimal places and give you control of how to handle rounding when the result of a calculation would give a non-exact number (like 2/3).
I’m a professional developer and do a lot of stuff with monetary values, some of it in Java.
Its interesting that I take the opposite view to you: a) I never use BigDecimal, and b) I always use ‘double’ (in Java that is, in C# you can write a proper Money class with mutable value semantics).
As regards a), because 1) large numbers of small immutable objects such as BigDecimal are worst-case garbage collected: the gain/work ratio of GC cycles is as low as you can get, and 2) the operations, lacking familiar operator overloading, are much more error-prone to write – and to read subsequently.
As regards b), because all a double approximation to an exact monetary value has to do is to maintain the deci-pence value accurately i.e. in xxx.yyz the ‘z’ decimal digit. This follows from the nature of monetary rounding: inspect the deci-pence value and if decimal 5 or more round up the pence value, otherwise don’t. (For pence here read ‘cents’ or whatever.) An IEEE double can maintain accuracy of the deci-pence value over a large range of monetary values, plenty large enough for my (and most) practical purposes. Agreed, if you were handling multiples of trillions of currency units (and were still bothered about single pence/cent accuracy) that would be another matter and you would have to resort to something like BigDecimal.
‘float’ should never be used for anything, of course – that is definitely something worth teaching the kids.
D.