BigDecimal and rounding modes

Categories:

After seeing the great success of introductory article on BigDecimal class I wanted to go on with some examples on one of the most complex operations: division.

The problem…

Is always the same: length :)

BigDecimal, as already seen, can have an arbitrary number of decimal digits: this does not mean they can go to infinity, indeed we have at our disposal something like four billions digits, and counting. Not bad, you could argue… well let’s try this at home: how many computers are needed to store all the decimals of “1 / 3”? Answer is simple: all the computers in this universe won’t be enough!

Moreover, if we try to do this division with BigDecimal we’ll get an ArithmeticException.

The solution

There are different methods to solve this problem, we will use an approximation tecnique. To better handle roundings, java.math.MathContext class has been introduced in JDK 1.5 (aka 5.0). It is used to store and configure precision and rounding settings.

Here is an example with the above problematic division, 1 / 3:

package it.megadix.math;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;

public class BigDecimalDivision {
    public static void main(String[] args) {
        BigDecimal n = new BigDecimal("1");
        BigDecimal BIGDEC_3 = new BigDecimal("3");
        try {
            // this instruction generates a java.lang.ArithmeticException
            System.out.println(n.divide(BIGDEC_3));
        } catch (Exception ex) {
            System.err.println(ex.getClass().getName() + ": " + ex.getMessage());
        }

        // create a MathContext with a limited number of decimals and "HALF_UP"
        // rounding mode (the one taught normally at school)
        MathContext mc = new MathContext(10, RoundingMode.HALF_UP);
        System.out.println(n.divide(BIGDEC_3, mc));
    }
}

Interesting parts:

  • plain division (without using MathContext) generates an ArithmeticException, because there is no limit to decimal progression, thus causing an overflow;

  • if we use MathContext instead, we don’t get an exception but result is approximated.

Program output:

java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
0.3333333333

Rounding modes

The available rounding modes are many, each having a purpose; see javadocs for details:

  • RoundingMode.CEILING: rounds towards positive infinity;

  • RoundingMode.FLOOR: rounds negative infinity;

  • RoundingMode.UP: rounds away from zero, considering absolute value;

  • RoundingMode.DOWN: rounds towards from zero, considering absolute value;

  • RoundingMode.HALF_UP: the usual algorithm taught at school;

  • RoundingMode.HALF_DOWN: similar to RoundingMode.HALF_UP, but negative infinity;

  • RoundingMode.HALF_EVEN: a mix of RoundingMode.HALF_UP and RoundingMode.HALF_DOWN, used to statistically minimize error on repeated operations;

  • RoundingMode.UNNECESSARY: default mode, assumes no rounding will be necessary (i.e. potentially leading to overflow).

Let’s se now some examples:

package it.megadix.math;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;

public class BigDecimalRounding {
    public static void main(String[] args) {
        BigDecimal n11 = new BigDecimal("0.11");
        BigDecimal n15 = new BigDecimal("0.15");
        BigDecimal n19 = new BigDecimal("0.19");
        BigDecimal neg_n11 = new BigDecimal("-0.11");
        BigDecimal neg_n15 = new BigDecimal("-0.15");
        BigDecimal neg_n19 = new BigDecimal("-0.19");

        // diversi tipi di MathContext
        MathContext mc_CEILING = new MathContext(1, RoundingMode.CEILING);
        MathContext mc_FLOOR = new MathContext(1, RoundingMode.FLOOR);
        MathContext mc_UP = new MathContext(1, RoundingMode.UP);
        MathContext mc_DOWN = new MathContext(1, RoundingMode.DOWN);
        MathContext mc_HALF_UP = new MathContext(1, RoundingMode.HALF_UP);
        MathContext mc_HALF_EVEN = new MathContext(1, RoundingMode.HALF_EVEN);
        MathContext mc_HALF_DOWN = new MathContext(1, RoundingMode.HALF_DOWN);
        MathContext mc_UNNECESSARY = new MathContext(1, RoundingMode.UNNECESSARY);

        print(n11, mc_CEILING);
        print(n15, mc_CEILING);
        print(n19, mc_CEILING);
        print(neg_n11, mc_CEILING);
        print(neg_n15, mc_CEILING);
        print(neg_n19, mc_CEILING);
        System.out.println("");

        print(n11, mc_FLOOR);
        print(n15, mc_FLOOR);
        print(n19, mc_FLOOR);
        print(neg_n11, mc_FLOOR);
        print(neg_n15, mc_FLOOR);
        print(neg_n19, mc_FLOOR);
        System.out.println("");

        print(n11, mc_UP);
        print(n15, mc_UP);
        print(n19, mc_UP);
        print(neg_n11, mc_UP);
        print(neg_n15, mc_UP);
        print(neg_n19, mc_UP);
        System.out.println("");

        print(n11, mc_DOWN);
        print(n15, mc_DOWN);
        print(n19, mc_DOWN);
        print(neg_n11, mc_DOWN);
        print(neg_n15, mc_DOWN);
        print(neg_n19, mc_DOWN);
        System.out.println("");

        print(n11, mc_HALF_UP);
        print(n15, mc_HALF_UP);
        print(n19, mc_HALF_UP);
        print(neg_n11, mc_HALF_UP);
        print(neg_n15, mc_HALF_UP);
        print(neg_n19, mc_HALF_UP);
        System.out.println("");

        print(n11, mc_HALF_EVEN);
        print(n15, mc_HALF_EVEN);
        print(n19, mc_HALF_EVEN);
        print(neg_n11, mc_HALF_EVEN);
        print(neg_n15, mc_HALF_EVEN);
        print(neg_n19, mc_HALF_EVEN);
        System.out.println("");

        print(n11, mc_HALF_DOWN);
        print(n15, mc_HALF_DOWN);
        print(n19, mc_HALF_DOWN);
        print(neg_n11, mc_HALF_DOWN);
        print(neg_n15, mc_HALF_DOWN);
        print(neg_n19, mc_HALF_DOWN);
        System.out.println("");

        // these instructions generate ArithmeticException
        try {
            print(n11, mc_UNNECESSARY);
        } catch (Exception ex) {
            System.err.println(ex.getClass().getName() + ": " + ex.getMessage());
        }
        try {
            print(n19, mc_UNNECESSARY);
        } catch (Exception ex) {
            System.err.println(ex.getClass().getName() + ": " + ex.getMessage());
        }
    }

    private static void print(BigDecimal n, MathContext mc) {
        System.out.println(n + " -> (RoundingMode." + mc.getRoundingMode().toString() + ") -> " + n.round(mc));
    }
}

Program output:

0.11 -> (RoundingMode.CEILING) -> 0.2
0.15 -> (RoundingMode.CEILING) -> 0.2
0.19 -> (RoundingMode.CEILING) -> 0.2
-0.11 -> (RoundingMode.CEILING) -> -0.1
-0.15 -> (RoundingMode.CEILING) -> -0.1
-0.19 -> (RoundingMode.CEILING) -> -0.1

0.11 -> (RoundingMode.FLOOR) -> 0.1
0.15 -> (RoundingMode.FLOOR) -> 0.1
0.19 -> (RoundingMode.FLOOR) -> 0.1
-0.11 -> (RoundingMode.FLOOR) -> -0.2
-0.15 -> (RoundingMode.FLOOR) -> -0.2
-0.19 -> (RoundingMode.FLOOR) -> -0.2

0.11 -> (RoundingMode.UP) -> 0.2
0.15 -> (RoundingMode.UP) -> 0.2
0.19 -> (RoundingMode.UP) -> 0.2
-0.11 -> (RoundingMode.UP) -> -0.2
-0.15 -> (RoundingMode.UP) -> -0.2
-0.19 -> (RoundingMode.UP) -> -0.2

0.11 -> (RoundingMode.DOWN) -> 0.1
0.15 -> (RoundingMode.DOWN) -> 0.1
0.19 -> (RoundingMode.DOWN) -> 0.1
-0.11 -> (RoundingMode.DOWN) -> -0.1
-0.15 -> (RoundingMode.DOWN) -> -0.1
-0.19 -> (RoundingMode.DOWN) -> -0.1

0.11 -> (RoundingMode.HALF_UP) -> 0.1
0.15 -> (RoundingMode.HALF_UP) -> 0.2
0.19 -> (RoundingMode.HALF_UP) -> 0.2
-0.11 -> (RoundingMode.HALF_UP) -> -0.1
-0.15 -> (RoundingMode.HALF_UP) -> -0.2
-0.19 -> (RoundingMode.HALF_UP) -> -0.2

0.11 -> (RoundingMode.HALF_EVEN) -> 0.1
0.15 -> (RoundingMode.HALF_EVEN) -> 0.2
0.19 -> (RoundingMode.HALF_EVEN) -> 0.2
-0.11 -> (RoundingMode.HALF_EVEN) -> -0.1
-0.15 -> (RoundingMode.HALF_EVEN) -> -0.2
-0.19 -> (RoundingMode.HALF_EVEN) -> -0.2

0.11 -> (RoundingMode.HALF_DOWN) -> 0.1
0.15 -> (RoundingMode.HALF_DOWN) -> 0.1
0.19 -> (RoundingMode.HALF_DOWN) -> 0.2
-0.11 -> (RoundingMode.HALF_DOWN) -> -0.1
-0.15 -> (RoundingMode.HALF_DOWN) -> -0.1
-0.19 -> (RoundingMode.HALF_DOWN) -> -0.2

java.lang.ArithmeticException: Rounding necessary
java.lang.ArithmeticException: Rounding necessary