Rounding

If we needed more accuracy we would use a BigDecimal, also known as a Fixed Point Decimal, for the averageWeight attribute as represented in the code below (however, there is a problem with the code, which is discussed following it):

var dTotalKgs    = ScriptUtil.createBigDecimal(0.0);
var dPlanetCount = 0;
for (var iterator = planets.planetList.listIterator(); iterator.hasNext(); )
{
     var planet = iterator.next();
     dTotalKgs = dTotalKgs.add(planet.weightKgs);
     dPlanetCount ++;
}
planets.averageWeight = dTotalKgs.divide(ScriptUtil.createBigDecimal(dPlanetCount));

This would work quite well now that we have 8 planets, but in the days before Pluto was downgraded to a Dwarf Planet we had nine planets, and attempting to divide a number by 9 often results in a recurring string of decimals if done exactly. This causes problems for BigDecimals, since the BigDecimal class stores numbers up to an arbitrary level of precision. However, it does NOT store numbers to an infinite level of precision, which would be required to store 1/9 = 0.111111…, for example. So when doing division operations that can result in recurring decimals or other overflows, Rounding Mode must be applied to the BigDecimal method that is being used.

There are two ways that Rounding Mode can be applied to the divide() method: either directly, or by way of a MathContext object that contains a precision and RoundingMode. If applied directly, it can be applied with or without the precision. There are eight possible values for RoundingMode: UP, DOWN, CEILING, FLOOR, HALF_UP, HALF_DOWN, HALF_EVEN, or UNNECESSARY. Additional details of the behavior regarding the different modes can be found in the reference section, but you can also see from the example here how different values are rounded according to the different RoundingModes.

Example Rounding Mode Results According to Single Digit Rounding Input
Input Number Up Down Ceiling Floor Half_up Half_down Half_even Unnecessary
5.5 6 5 6 5 6 5 6 5.5
2.5 3 2 3 2 3 2 2 2.5
1.6 2 1 2 1 2 2 2 1.6
1.1 2 1 2 1 1 1 1 1.1
1.0 1 1 1 1 1 1 1 1.0
-1.0 -1 -1 -1 -1 -1 -1 -1 -1.0
-1.1 -2 -1 -1 -2 -1 -1 -1 -1.1
-1.6 -2 -1 -1 -2 -2 -2 -2 -1.6
-2.5 -3 -2 -2 -3 -3 -2 -2 -2.5
-5.5 -6 -5 -5 -6 -6 -5 -6 -5.5

There are three built-in MathContexts provided: DECIMAL32, DECIMAL64, and DECIMAL128. If the DECIMAL64 MathContext is used, then a precision (number of significant figures) and Rounding Mode (HALF_UP) equivalent to that used by the Floating Point Decimal sub-type (which is the same as the Double type used in languages such as Java and C, and the Numeric type used in JavaScript) is used.

The UNNECESSARY rounding mode is the default RoundingMode, and will generate an exception if the resultant number of digits used to represent the result is more than the defined precision.

The table below shows the results of dividing 12345 by 99999 using BigDecimals with different Precisions, RoundingModes, and MathContexts.

Example Rounding Mode Results According to BigDecimals Digit Rounding Input
Rounding Mode Precision Math Context Result
      java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
UP     1
HALF_UP     0
UP 10   0.1234512346
HALF_UP 10   0.1234512345
UP 50   0.12345123451234512345123451234512345123451234512346
HALF_UP 50   0.12345123451234512345123451234512345123451234512345
    DECIMAL32 0.1234512
    DECIMAL64 0.1234512345123451
    DECIMAL128 0.1234512345123451234512345123451235
      java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.

The divide() method called above should be changed to:

// 30 significant digits, rounding 0.5 up
planets.averageWeight = totalKgs.divide(ScriptUtil.createBigDecimal(planetCount),30,RoundingMode.HALF_UP);

or

// 34 significant digits, rounding 0.5 up
planets.averageWeight = totalKgs.divide(ScriptUtil.createBigDecimal (planetCount),
MathContext.DECIMAL128);

or

// 30 significant digits, rounding 0.5 up (using MathContext)
var mc = ScriptUtil.createMathContext(30,RoundingMode.HALF_UP);
planets.averageWeight = totalKgs.divide(ScriptUtil.createBigDecimal (planetCount),mc);

When comparing BigDecimal values it is best to use the compareTo() method, as the equals() method considers 1.4 to differ from 1.40. This is because 1.4 is stored in BigDecimal as the number 14 with a scale of 1 and a precision (for example, number of digits) of 2. 1.40 is stored as the number 140 with a scale of 2 and a precision of 3. The equals() method does not recognize them as the same. However, the compareTo() method sees that there is no difference between them, and returns 0, meaning they have the same value.