Categories
Coding

Overflowing BigDecimals

If you do a lot of numerical calculations in Java where control over precision and rounding is important (especially currency-related calculations), the BigDecimal class should be your first port of call. They encapsulate an integer unscaled value, and a 32-bit precision scaling factor, which provides a large range of values.

If we define a variable called dividendTax as a BigInteger like so:

private BigDecimal dividendTax;

with a scaling factor of 2 (for currency) and a suitable rounding mode:

static final Integer SCALE = 2;
public static final RoundingMode ROUND_MODE = RoundingMode.HALF_UP;

We can round and scale an intermediate BigInteger-based calculation to a Double value like so:


public Double getDividendTax() {
return dividendTax.setScale(Constants.SCALE, Constants.ROUND_MODE).doubleValue();
}

Operations like divide() are methods on the BigDecimal instances themselves:

public Double getMonthlyTaxableAmount() {
return annualDividendTax.divide(new BigDecimal(12.0).setScale(Constants.SCALE, Constants.ROUND_MODE).doubleValue();
}

The above code snippet is possibly dangerous, in that the divide() operation can throw an ArithmeticException if the result from divide() is an infinite expansion (e.g. 1 divided by 3). This happened to me a couple of times until I figured what was missing.

The key is to pass a MathContext instance which specifies the correct precision to use. By default, the BigDecimal arithmetical operators use unlimited precision, which will obviously fail if a division operation results in an infinite series. So you need to truncate the results using one of the predefined precision ranges:

public Double getMonthlyTaxableAmount() {
return annualDividendTax.divide(new BigDecimal(12.0), MathContext.DECIMAL32).setScale(Constants.SCALE, Constants.ROUND_MODE).doubleValue();
}

Keep this in mind if you are using division operations that may potentially give infinite-precision results.