How to use Java’s BigInteger and BigDecimal classes

Overview

Java numeric classes can sometimes be very confusing, therefore I wanted to share my experience with them, specifically the BigInteger and BigDecimal ones.

Creating the values

Even though BigInteger and BigDecimal have the same goal (to represent infinitely growing numbers) the way the construct their values differs somewhat. Consider the following snippet of Java code:

long aLong = 123456789987654321l;
BigInteger bigInt = new BigInteger(aLong);
BigDecimal bigDec = new BigDecimal(aLong);

Surprisingly, we get a compilation error for the BigInteger constructor. Then how are we supposed to convert our primitive numeric values? In such cases it’s best to check if there are any available static constructors (static methods that return an object of the class). Therefore we can rewrite the erroneous line like so:

BigInteger bigInt = BigInteger.valueOf(aLong);

Another interesting discovery is that you can parse String values to BigInteger with a custom radix without having to resort to black magic:

String aString = "1F4D";
BigInteger bigInt = new BigInteger(aString, /* radix */ 16);

Dealing with aggregates

The old-style of finding the sum or product of a numeric class in Java is very verbose and contains a lot of boilerplate code:

BigInteger result = BigInteger.Zero;
for (BigInteger bigInt : bigIntCollection) {
    result = result.add(bigInt);
}

It is imperative that we not forget the assignment back to the result variable, lest we lose our actual result :o) We can much quickly and safely get the same result by using the Stream API that was introduced in Java 8:

BigInteger result = bigIntCollection.stream()
        .reduce(BigInteger.ZERO, BigInteger::add);

Ain’t that sweet?

Rounding hell

Dividing BigDecimal values is harder than it sounds. Why? Because if the result of the division is not an exact result, which in most cases isn’t, then you’ll get slapped by an exception (in the face). To avoid the lovely ArithmeticException we should always supply a limited MathContext or scale and rounding parameters:

BigDecimal result1 = bigDec1.divide(bigDec2, MathContext.DECIMAL128);
BigDecimal result2 = bigDec1.divide(bigDec2, /* scale */ 9, RoundingMode.HALF_UP);

Additionally we can “re-scale” a value to a more generous or limiting decimal constraint. Some accounting tasks may require this kind of calculation 😉 Like dragon accounting 😀

BigDecimal result = bigDec1.divide(bigDec2, /* scale */ 9, RoundingMode.UP);
BigDecimal scaled = result.scale(/* scale */ 7, RoundigMode.DOWN);

Formatting values

In the end all values must be presented to the user in a friendly way. Outputting values with thousands of digits is an easy way to scare potential customers away. To our rescue comes the DecimalFormat class:

DecimalFormat formatter = new DecimalFormatter("0.####");
formatter.format(new BigDecimal(123456789.87654321);

The downside is, it’s going to use the current locale to output the decimal separator and to override it requires some ugly looking code like:

DecimalFormat formatter = new DecimalFormatter("0.####",
        new DecimalFormatSymbols(Locale.US));

What about if we want to use custom rounding to the values?

formatter.setRoundingMode(RoundingMode.DOWN);

– This is madness!
– Madness? THIS. IS. JAVAAAAAAAAA!!!

A good alternative to all of this verbosity is to use the String.format or MessageFormat APIs. Here’s an example with String.format:

String.format(Locale.US, "%.4f", bigDec.setScale(4, RoundingMode.DOWN));

Happy coding!