Java BigDecimal Gotchas

While working with BigDecimal for the last few months, I (and other colleages) have stumbled across some of the peculiarities of BigDecimal. Here’s a list of gotchas, tips, and tricks you might want to keep in mind when working with BigDecimal. There’s source code samples with the output they create, so you can copy these and try on your own.

BigDecimal has some design issues (though not as bad as Java’s Date), and monetary calculations are a field of their own as well, so keep your eyes open, write lots of tests, and think of corner cases.

Immutability

BigDecimal is immutable (which is good for design as it enforces value semantics, but bad for performance as each operations implies an object creation), thus it has to be used like this, each operation returning a new object:


// 5
System.out.println(BigDecimal.valueOf(2).add(BigDecimal.valueOf(3)));

Constructor vs. valueOf

There’s already plenty of discussions why the BigDecimal(double) constructor is evil.
Take care when converting from BigDecimal to double, the BigDecimal(double) constructor might not do what you expect. The reason is that certain values such as 0.1 are just not exactly representable as double and using this constructor will pick up that small error, too. Just use the BigDecimal(String) constructor

// 1234.56
System.out.println(BigDecimal.valueOf(1234.56));
// 1234.55999999999994543031789362430572509765625
System.out.println(new BigDecimal(1234.56));
// best option: use the string constructor
// 1234.56
System.out.println(new BigDecimal("1234.56"));

Don’t use == on BigDecimals

BigDecimals are reference types – you have to use equals (or rather compareTo, as you’ll see further below).
For certain values (0 trough 10) there’s an internal cache for those commonly used values.

// false
System.out.println(BigDecimal.valueOf(11) == BigDecimal.valueOf(11));
// true
System.out.println(BigDecimal.valueOf(11).equals(BigDecimal.valueOf(11)));
// true
System.out.println(BigDecimal.valueOf(10) == BigDecimal.valueOf(10));

equals is broken

BigDecimal.equals is problematic. Essentially, you can only use it when you’re guaranteed to always have values with the same scale. Therefore, take care when checking for equality – BigDecimal has a scale, which is also used in the equality comparison. The compareTo method, on the other hand, ignores the scale. Equals not behaving the same way as compareTo is quite counter-intuitive.

// false
System.out.println(new BigDecimal("0.0").equals(new BigDecimal("0.00")));
// false
System.out.println(new BigDecimal("0.0").hashCode() == (new BigDecimal("0.00")).hashCode());
// true
System.out.println(new BigDecimal("0.0").compareTo(new BigDecimal("0.00")) == 0);

compareTo Idiom

Use the compareTo idiom which is suggested in the compareTo javadoc. It’s just so much easier to understand.

// essentially readable as 1 < = 2
// true
System.out.println(BigDecimal.valueOf(1).compareTo(BigDecimal.valueOf(2)) <= 0);
[/codesyntax]
 
 
 
Always use a scale for divisions
When performing divisions, I strongly recommend that you specify a rounding mode. 1 / 3 = 0.33333..., which, without rounding, will throw an exception.
[codesyntax lang="java"]
//java.lang.ArithmeticException: Non-terminating decimal expansion;
//no exact representable decimal result.
try
{
	BigDecimal.valueOf(1).divide(BigDecimal.valueOf(3));
} catch (ArithmeticException ex)
{
	System.out.println(ex.getMessage());
}
// always use a scale and the roundingmode of your choice
// 0.33
System.out.println(BigDecimal.valueOf(1).divide(BigDecimal.valueOf(3), 2, RoundingMode.HALF_UP));

Division by zero

This should be a no-brainer, but is still a topic.

// Division by zero
try
{
	BigDecimal.valueOf(1).divide(BigDecimal.ZERO);
} catch (ArithmeticException ex)
{
	System.out.println(ex.getMessage());
}

BigDecimal can't handle all double numbers

BigDecimal doesn't have a representation for NaNs etc.

// java.lang.NumberFormatException: Infinite or NaN
try
{
	System.out.println(new BigDecimal(Double.NaN));
}
catch (NumberFormatException ex)
{
	System.out.println(ex.getMessage());
}

Precision != Scale

BigDecimal has two different notions for limiting numbers, they're completely different, even thought the method signatures would suggest otherwise. This is IMO a (yet another) design flaw in BigDecimal.

  • Precision is the overall limit of, well, precision, in a number. E.g. 1234 and 12.34 have the same precision (4 decimal digits)
  • Scale is the limit of the digits after the decimal point 3.34 and 234.25 have the same scale (2 digits after the decimal point)

BigDecimal hundred = BigDecimal.valueOf(100);
BigDecimal three = BigDecimal.valueOf(3);
// 33.33
System.out.println(hundred.divide(three, 2, RoundingMode.HALF_UP));
// 33
System.out.println(hundred.divide(three, new MathContext(2, RoundingMode.HALF_UP)));

Rounding

There's various rounding modes, carefully read the javadoc.

// 1.01
System.out.println(new BigDecimal("1.005").setScale(2, RoundingMode.HALF_UP));
// 1.00
System.out.println(new BigDecimal("1.005").setScale(2, RoundingMode.HALF_DOWN));

Stay in BigDecimal Land

Sometimes it is tempting to just use double, as it's simpler to use with its operator overloading. Don't do it. When working with BigDecimals, stay within their realm.

//false
System.out.println(new BigDecimal("10e15").doubleValue() > 10e15);
//still false
System.out.println(new BigDecimal("10e15").add(BigDecimal.ONE).doubleValue() > 10e15);
//rather use this, stick with BigDecimals
//true
System.out.println(new BigDecimal("10e15").add(BigDecimal.ONE).compareTo(BigDecimal.valueOf(10e15)) > 0);

Performance

BigDecimal operations are (naturally) orders of magnitude slower than calculations with int or long (as each BigDecimal operation involves an object creation). But the BigDecimal operations per second you can expect on a modern machine still number in the millions.

// 2
System.out.println(4 / 2);
// at least a hundred times slower
// 2
System.out.println(BigDecimal.valueOf(4).divide(BigDecimal.valueOf(2), 2, RoundingMode.HALF_EVEN));

Constants

If you have numbers that you use often, you might consider drawing them into constants so that the BigDecimal isn't instanciated each time. This is ok, because they're immutable.

// 1000
System.out.println(BigDecimal.valueOf(10).multiply(HUNDRED)));
//There's some built-in constants, use them when appropriate
// 0
System.out.println(BigDecimal.ZERO);

Parsing BigDecimals

Because BigDecimals might be big numbers, there's a special way of dealing with parsing BigDecimals.

NumberFormat numberFormat = NumberFormat.getInstance();
// As the NumberFormat javadoc explains, in most locales it's ok to cast to DecimalFormat
// in production code, there should be an instanceof check
final DecimalFormat decimalFormat = (DecimalFormat) numberFormat;
// 1.0E25
System.out.println(decimalFormat.parse("10000000000000000000000001.01"));
decimalFormat.setParseBigDecimal(true);
// 10000000000000000000000001.01
System.out.println(decimalFormat.parse("10000000000000000000000001.01"));

Signum

There's signum, use it when appropriate (IMO more concise than x.compareTo(BigDecimal.ZERO) == 0)

//-1
System.out.println(BigDecimal.valueOf(-2).signum());

General rounding topics in monetary calculations

One thing that comes along when using rounded calculations is that multiplication and division operations are not reversible, and that certain calculations just plain are not possible.

Irreversible operations

For example, when calculating with 2 decimal places with the German VAT of 19%, there's no net price with 2 decimal places so you can have a gross price of 9.99

9.99 / 1.19 = 8.3949 -> 8.43

8.39 * 1.19 = 9.9841 - > 9.98
8.40 * 1.19 = 9.996 - > 10.00

So there's just no net price for representing a gross price of 9.99. You can either live with it, calculate with a higher precision, or use some correction mechanism where errors cancel each other out over multiple calculations.

Rounding errors in sums

A related topic applies to sum calculations of rounded values. If you've got a sum that you calculate both in gross and net (assuming a VAT of 19%)

Net           Gross
0.01          0.01
0.01          0.01
0.01          0.01
-----------------------
0.03          0.03
0.03 * 1.19 = 0.04

So there's a mismatch between the summed up gross price and the gross sum calculated from the net sum.
This can be solved by tweaking one (usually the biggest) net price in the sum so that the total result is ok again.

If you don't (want to) use BigDecimal

For monetary calculations, if you can't use BigDecimal, I would tend to recommend using longs or ints over double. I've seen homegrown double calculations where 0.00499999999 would be rounded to ... you can guess it.

This entry was posted in Software. Bookmark the permalink.

15 Responses to Java BigDecimal Gotchas

  1. Pingback: Java BigDecimal | porady InformaTyczne eduIT.pl

Leave a Reply

Your email address will not be published. Required fields are marked *

Please solve this little equation for verifying that you\'re human *