NumberFormat rounding issue with Java 8 only
java, java-8
Solution
I could track down this issue to class `java.text.DigitList` line 522.
The situation is that it thinks the decimal digits `6.0289` are already rounded (which is correct when comparing to the equivalent `BigDecimal` representation `6.208899…`) and decides to not round up again. The problem is that this decision makes sense only in the case that the digit resulting from rounding up is `5`, not when it is bigger than `5`. Note how the code for `HALF_DOWN` correctly differentiates between the `digit=='5'` and `digit>'5'` case.
This is a bug, obviously, and a strange one given the fact that the code for doing similar right (just for the other direction) is right below the broken one.
case HALF_UP:
if (digits[maximumDigits] >= '5') {
// We should not round up if the rounding digits position is
// exactly the last index and if digits were already rounded.
if ((maximumDigits == (count - 1)) &&
(alreadyRounded))
return false;
// Value was exactly at or was above tie. We must round up.
return true;
}
break;
case HALF_DOWN:
if (digits[maximumDigits] > '5') {
return true;
} else if (digits[maximumDigits] == '5' ) {
if (maximumDigits == (count - 1)) {
// The rounding position is exactly the last index.
if (allDecimalDigits || alreadyRounded)
/* FloatingDecimal rounded up (value was below tie),
* or provided the exact list of digits (value was
* an exact tie). We should not round up, following
* the HALF_DOWN rounding rule.
*/
return false;
else
// Value was above the tie, we must round up.
return true;
}
// We must round up if it gives a non null digit after '5'.
for (int i=maximumDigits+1; i<count; ++i) {
if (digits[i] != '0') {
return true;
}
}
}
break;
The reason why this doesn’t happen to the other number is that `6.2088` is not the result of rounding up (again, compare to the `BigDecimal` output `6.208800…`). So in this case it will round up.
Problem
Can somebody explain to me why the following code: ``` public class Test { public static void main(String... args) { round(6.2088, 3); round(6.2089, 3); } private static void round(Double num, int numDecimal) { System.out.println("BigDecimal: " + new BigDecimal(num).toString()); // Use Locale.ENGLISH for '.' as decimal separator NumberFormat nf = NumberFormat.getInstance(Locale.ENGLISH); nf.setGroupingUsed(false); nf.setMaximumFractionDigits(numDecimal); nf.setRoundingMode(RoundingMode.HALF_UP); if(Math.abs(num) - Math.abs(num.intValue()) != 0){ nf.setMinimumFractionDigits(numDecimal); } System.out.println("Formatted: " + nf.format(num)); } } ``` gives the following output? ``` [me@localhost trunk]$ java Test BigDecimal: 6.208800000000000096633812063373625278472900390625 Formatted: 6.209 BigDecimal: 6.208899999999999863575794734060764312744140625 Formatted: 6.208 ``` In case you don't see it: "6.2089" rounded to 3 digits gives the output "6.208" while "6.2088" gives "6.209" as output. Less is more? The results were good when using Java 5, 6 or 7 but this Java 8 gives me this strange output. Java version: ``` [me@localhost trunk]$ java -version java version "1.8.0_05" Java(TM) SE Runtime Environment (build 1.8.0_05-b13) Java HotSpot(TM) Server VM (build 25.5-b02, mixed mode) ``` EDIT: this is Java 7's output: ``` [me@localhost trunk]$ java Test BigDecimal: 6.208800000000000096633812063373625278472900390625 Formatted: 6.209 BigDecimal: 6.208899999999999863575794734060764312744140625 Formatted: 6.209 ``` Java 7 version: ``` [me@localhost trunk]$ java -version java version "1.7.0_51" Java(TM) SE Runtime Environment (build 1.7.0_51-b13) Java HotSpot(TM) Server VM (build 24.51-b03, mixed mode) ```