Java 7和Java 8之间的舍入不一致是一个错误吗?

时间:2014-04-01 22:14:23

标签: java rounding

我发现java 7和java 8之间DecimalFormat类的舍入不一致。这是我的测试用例:

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));
    }
}

在Java(TM)SE运行时环境(版本1.7.0_51-b13)中,输出为:

83.6

在Java(TM)SE运行时环境(版本1.8.0-b132)中,输出为:

83.7

这是一个回归错误吗?或者随着Java 8的发布,舍入规则发生了变化?

2 个答案:

答案 0 :(得分:22)

看起来这是JDK 7中一个长期存在的错误,最终得到修复。例如见:

有一个计划草案,向JDK 8提供以下建议,解释了这个问题:

  

----------------------------------------------- ----------------------区域:核心库/ java.text

     

概要:修复了JDK7错误的舍入行为。该   NumberFormat / DecimalFormat format()方法的舍入行为   当价值非常接近准确地坐在一个平局时改变了   格式化模式中指定的舍入位置。

     

不相容的性质:行为

     

描述:使用NumberFormat / DecimalFormat类时,   以前的JDK版本的舍入行为在某些角落是错误的   案例。调用format()方法时发生了这种错误的行为   一个非常接近平局的值,同时指定舍入位置   通过使用的NumberFormat / DecimalFormat实例的模式   正好坐在领带的位置。在那种情况下错误的双倍   发生了舍入或错误的非舍入行为。

     

例如,使用默认推荐的NumberFormatFormat API   形式:NumberFormat nf = java.text.NumberFormat.getInstance()紧随其后   按nf.format(0.8055d),值0.8055d在计算机中记录为   0.80549999999999999378275106209912337362766265869140625因为此值无法以二进制格式精确表示。这里默认   舍入规则是“半偶数”,并且调用format()的结果   JDK7的输出错误为“0.806”,而正确的结果为“0.805”   因为计算机在存储器中记录的值是“低于”平局。

     

此新行为也适用于所有舍入位置   可能由程序员选择的任何模式定义(非   默认的)。

     

RFE         7131459

     

答案 1 :(得分:15)

正如在此问题的其他答案中所提到的,JDK 8对问题JDK-7131459: DecimalFormat produces wrong format() results when close to a tie中的DecimalFormat四舍五入进行了故意更改。

但是,这些更改引入了一个真正的错误,其归档为JDK-8039915: Wrong NumberFormat.format() HALF_UP rounding when last digit exactly at rounding position greater than 5。例如:

99.9989 -> 100.00
99.9990 ->  99.99

简单地说,这表明更高的数字围绕向下,而较低的数字围绕向上 :{{1 }}。它似乎只影响(x <= y) != (round(x) <= round(y))舍入模式,这是在小学运算类中教授的一种舍入:总是从零开始0.5轮。

此问题存在于Java 8的Oracle和OpenJDK版本中,并且更新了8u5,8u11,8u20,8u25和8u31。

Oracle修复了Java 8 update 40

中的这个错误

非官方运行时补丁可用于早期版本

感谢Holger this answer对相关问题的研究,我能够开发一个运行时补丁,我的雇主已经根据GPLv2许可的条款免费发布了它异常 1 (与OpenJDK源代码相同)。

补丁项目和源代码是hosted on GitHub,其中包含有关此错误的更多详细信息以及可下载二进制文件的链接。该补丁在运行时工作,因此不会对磁盘​​上​​的Java文件进行任何修改,并且应该可以安全地在所有版本的Oracle Java&gt; = 6上使用,至少通过版本8(包括u40及更高版本)。

1 我不是律师,但我的理解是 GPLv2 w / CPE 允许以二进制形式进行商业用途,而不适用于GPL合并后的工作。