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 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).