Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is inconsistency in rounding between Java 7 and Java 8 a bug?

Tags:

java

rounding

I'm seeing inconsistencies in rounding in the DecimalFormat class between java 7 and java 8. Here is my test case:

DecimalFormatTest.java

import java.text.DecimalFormat;  public class DecimalFormatTest {     public static void main(String[] args) {         DecimalFormat format = (DecimalFormat) DecimalFormat.getInstance();         format.setDecimalSeparatorAlwaysShown(true);         format.setMinimumFractionDigits(1);         format.setMaximumFractionDigits(1);         System.out.println(format.format(83.65));     } } 

In Java(TM) SE Runtime Environment (build 1.7.0_51-b13) the output is:

83.6 

In Java(TM) SE Runtime Environment (build 1.8.0-b132) the output is:

83.7 

Is this a regression bug? Or were the rounding rules changed with the release of Java 8?

like image 480
Asaph Avatar asked Apr 01 '14 22:04

Asaph


2 Answers

It looks like this was a long-standing bug in JDK 7 that was finally fixed. See for example:

  • https://bugs.openjdk.java.net/browse/JDK-8029896
  • https://bugs.openjdk.java.net/browse/JDK-7131459

There is a draft plan to provide the following advisory with JDK 8 which explains the issue:

--------------------------------------------------------------------- Area: Core Libraries / java.text

Synopsis: A wrong rounding behavior of JDK7 has been fixed. The rounding behavior of NumberFormat/DecimalFormat format() method has changed when value is very close to a tie sitting exactly at the rounding position specified in the formatting pattern.

Nature of Incompatibility: behavioral

Description: When using NumberFormat/DecimalFormat classes, the rounding behavior of previous JDK versions was wrong in some corner cases. This wrong behaviour happened when calling format() method with a value that is very close to a tie, while rounding position specified by the pattern of the NumberFormat/DecimalFormat instance used is exactly sitting at the position of the tie. In that case wrong double rounding or erroneous non-rounding behavior happened.

As an example, while using default recommended NumberFormatFormat API form: NumberFormat nf = java.text.NumberFormat.getInstance() followed by nf.format(0.8055d), value 0.8055d is recorded in the computer as 0.80549999999999999378275106209912337362766265869140625 since this value cannot be represented exactly in binary format. Here default rounding rule is "half-even", and the result of calling format() in JDK7 is a wrong output of "0.806", while correct result is "0.805" since value recorded in memory by the computer is "below" the tie.

This new behavior is also implemented for all rounding positions that might be defined by any pattern chosen by the programmer (non default ones).

RFE 7131459


like image 186
mellamokb Avatar answered Sep 19 '22 14:09

mellamokb


As mentioned in other answers to this question, JDK 8 made intentional changes to DecimalFormat rounding in issue JDK-7131459: DecimalFormat produces wrong format() results when close to a tie.

However, those changes introduced a real bug filed as JDK-8039915: Wrong NumberFormat.format() HALF_UP rounding when last digit exactly at rounding position greater than 5. For example:

99.9989 -> 100.00 99.9990 ->  99.99 

To put it simply, this demonstrates a case where a higher number rounds down, and a lower number rounds up: (x <= y) != (round(x) <= round(y)). It appears to only affect the HALF_UP rounding mode, which is the kind of rounding taught in grade school arithmetic classes: 0.5 rounds away from zero, always.

This issue exists in both Oracle and OpenJDK releases of Java 8 and updates 8u5, 8u11, 8u20, 8u25, and 8u31.

Oracle fixed this bug in Java 8 update 40

  • Bug 8039915 tracks the OpenJDK fix for Java 9
  • Bug 8061380 tracks the backport to OpenJDK 8
  • Public GA release of 8u40 with JDK 1.8.0_40-b25 on 03 March 2015 (release notes)

An unofficial runtime patch is available for earlier versions

Thanks to research by Holger in this answer to a related question, I was able to develop a runtime patch and my employer has released it free under the terms of the GPLv2 license with Classpath Exception1 (the same as the OpenJDK source code).

The patch project and source code is hosted on GitHub with more details about this bug as well as links to downloadable binaries. The patch works at runtime so no modifications are made to the Java files on disk, and it should be safe for use on all versions of Oracle Java >= 6 and at least through version 8 (including u40 and later).

1 I am not a lawyer, but my understanding is that GPLv2 w/ CPE allows commercial use in binary form without GPL applying to the combined work.

like image 27
William Price Avatar answered Sep 17 '22 14:09

William Price