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

2022-09-01 06:09:36

我看到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的发布,舍入规则发生了变化?


答案 1

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

有一个计划草案,为JDK 8提供以下咨询,以解释这个问题:

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

剧情简介:修复了 JDK7 的错误舍入行为。当值非常接近正好位于格式设置模式中指定的舍入位置的平局时,NumberFormat/DecimalFormat format() 方法的舍入行为已更改。

不相容性的性质:行为

描述:使用数字格式/十进制格式类时,以前 JDK 版本的舍入行为在某些极端情况下是错误的。当使用非常接近并列的值调用 format() 方法时,会发生这种错误的行为,而由所使用的 NumberFormat/DecimalFormat 实例的模式指定的舍入位置正好位于并列的位置。在这种情况下,发生了错误的双重舍入或错误的非舍入行为。

例如,在使用默认推荐的 API 表单时:后跟 ,值将记录在计算机中,因为此值不能以二进制格式精确表示。这里默认的舍入规则是“半偶数”,在JDK7中调用format()的结果是“0.806”的错误输出,而正确的结果是“0.805”,因为计算机记录在内存中的值“低于”平局。NumberFormatFormatNumberFormat nf = java.text.NumberFormat.getInstance()nf.format(0.8055d)0.8055d0.80549999999999999378275106209912337362766265869140625

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

射频测量7131459



答案 2

正如在对这个问题的其他答案中提到的,JDK 8 在问题 JDK-7131459 中对舍入进行了有意的更改:DecimalFormat 在接近平局时会产生错误的 format() 结果DecimalFormat

但是,这些更改引入了一个真正的错误,即JDK-8039915:错误的NumberFormat.format()HALF_UP舍入,当最后一个数字恰好位于舍入位置大于5时。例如:

99.9989 -> 100.00
99.9990 ->  99.99

简单地说,这演示了一种情况,其中较高的数字向下舍入,而较小的数字向上舍入:。它似乎只影响舍入模式,这是小学算术课上教授的那种舍入模式:总是0.5轮远离零。(x <= y) != (round(x) <= round(y))HALF_UP

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

Oracle 修复了 Java 8 update 40 中的此 bug

非官方运行时修补程序可用于早期版本

多亏了 Holger回答相关问题时所做的研究,我才能够开发一个运行时补丁,我的雇主已经根据 GPLv2 许可证的条款免费发布了它,并带有 Classpath Exception1(与 OpenJDK 源代码相同)。

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

1 我不是律师,但我的理解是,GPLv2 w/ CPE 允许以二进制形式进行商业使用,而无需 GPL 应用于组合工作。