首页 > 解决方案 > 对于 x.715 和 x.915 使用 half_even 的 java 舍入错误

问题描述

当使用RoundingMode.HALF_EVEN (默认) for时java.text.DecimalFormat,我看到返回值异常。

我意识到存在精度错误,请参阅: Java BigDecimal setScale and rounding with half_even

但是,由于这是java.text.DecimalFormat我预计会处理的

定义:

HALF_EVEN
Rounding mode to round towards the "nearest neighbor" unless both neighbors are equidistant, in which case, round towards the even neighbor.

这是检查双舍入的示例:

import java.text.DecimalFormat;
import java.math.RoundingMode;

public class MyClass {
    public static void main(String args[]) {
        String[] dStrings= new String[] { "1548.015", "1548.115", "1548.215", "1548.515", "1548.715", "1548.815", "1548.915",    "1548.705", "1548.715", "1548.725", "1548.755", "1548.775", "1548.785", "1548.795",   "1548.710", "1548.711", "1548.712", "1548.715", "1548.717", "1548.718", "1548.719" } ;
        DecimalFormat df = new DecimalFormat("0.00");
        df.setMaximumFractionDigits(2);

        Double dValue;
        System.out.println("round mode:half_even...");
        System.out.println("String   => str-frmt| dec-frmt");
        //df.setRoundingMode(RoundingMode.CEILING);
        for (String dStr : dStrings) {
            //dValue = new Double(dStr);
            //dValue = Double.parseDouble(dStr);
            dValue = Double.valueOf(dStr);
            System.out.println(String.format("%s => %.2f | BD: %s |DF %s", dStr, dValue, BigDecimal.valueOf(dValue).setScale(2, RoundingMode.HALF_EVEN), df.format(dValue) ));
        }
    }
}

第4 个值使用 进行四舍五入java.text.DecimalFormat,第一个值是原始值

1548.015 => 1548.02 | BD: 1548.02 |DF 1548.02
1548.115 => 1548.12 | BD: 1548.12 |DF 1548.12
1548.215 => 1548.22 | BD: 1548.22 |DF 1548.21 - not ok
1548.515 => 1548.52 | BD: 1548.52 |DF 1548.52
1548.715 => 1548.72 | BD: 1548.72 |DF 1548.71 - not ok
1548.815 => 1548.82 | BD: 1548.82 |DF 1548.82
1548.915 => 1548.92 | BD: 1548.92 |DF 1548.91 - not ok

最近的邻居是上面列出的每种情况下的 1 个,并且 5 个是等距的,对吗?所以所有这些案件都应该是2,对吗?

我在多个当前和过去的 JDK 版本上进行了测试,结果都相同。是我误解还是这是一个错误?请解释为什么java.text.DecimalFormat不注意浮点精度,因为这似乎是问题所在。

另外,为什么在 String.format 中使用的基本舍入模型'%.2f'未在中表示java.math.RoundingMode?即在 5 及以上时四舍五入最近的邻居。

标签: java

解决方案


答案很复杂,不足之处在于不能依赖基于浮点的舍入,并且所看到的行为是正确的,正如 Basil 所指出的那样。舍入输入时,不应使用浮点数。

最好的解决方案,是最简单和最明显的例子: String.format("%s => %.2f", strDecimal)

Oracle SR 响应:

JDK7 是错误的行为(请参阅 JDK-7131459),而.JDK8 是正确的行为。

问题是“0.5555”,我们都理解为十进制值0.5555不能用任何二进制表示(包括IEEE-754)来表示。可以得到的这个数字的最精确的二进制表示是 0.55549999999999999378275106209912337362766265869140625 。可以使用提供无限精度的 BigDecimal 获得这种可能的最佳近似值:

System.out.println("0.5555 的 BigDecimal 输出:" + new BigDecimal(0.5555).toString());

以上将返回:“BigDecimal output for 0.5555 : 0.55549999999999999378275106209912337362766265869140625”

因此我们可以看到 0.5555 的最接近的二进制表示略小于 0.5555 (0.555499 ...)。

因此,即使程序员打算使用十进制 0.5555,计算机也可以使用 IEEE-754 的最佳近似值,这类似于 0.55549999999999999……这再次小于 0.5555 十进制值。

在HALF-EVEN四舍五入规则下,在小数点后第3位进行四舍五入时,如上述程序中的情况,该小数位不应进行四舍五入,这里的正确结果是“55.5%”

这里JDK7是错误的,JDK8是正确的。

使用 -XX:+AggressiveOpts 选项(从 7u14-b13 开始)可以获得正确的 JDK7 行为。

*** 了解几乎所有浮点值都不能用任何二进制表示精确记录是非常重要的(如此处的 IEEE-754)。使用浮点,你看到的不是你得到的!

文档供参考:http ://docs.oracle.com/cd/E19957-01/800-7895/800-7895.pdf

根据我发现的内容以及从其他工程师那里获得的信息,这不是 java 8 及更高版本的问题,而是关于浮点小数的预期行为。该实现基于 IEEE-754 标准。


推荐阅读