NumberFormat舍入问题仅限Java 8

And*_*ois 15 java java-8

有人可以向我解释为什么以下代码:

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

给出以下输出?

[me@localhost trunk]$ java Test
BigDecimal: 6.208800000000000096633812063373625278472900390625
Formatted: 6.209
BigDecimal: 6.208899999999999863575794734060764312744140625
Formatted: 6.208
Run Code Online (Sandbox Code Playgroud)

如果你没有看到它:"6.2089"四舍五入到3位给出输出"6.208"而"6.2088"给出"6.209"作为输出.少即是多?

使用Java 5,6或7时结果很好但是这个Java 8给了我这个奇怪的输出.Java版本:

[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)
Run Code Online (Sandbox Code Playgroud)

编辑:这是Java 7的输出:

[me@localhost trunk]$ java Test
BigDecimal: 6.208800000000000096633812063373625278472900390625
Formatted: 6.209
BigDecimal: 6.208899999999999863575794734060764312744140625
Formatted: 6.209
Run Code Online (Sandbox Code Playgroud)

Java 7版本:

[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)
Run Code Online (Sandbox Code Playgroud)

Hol*_*ger 19

我可以将这个问题追溯到java.text.DigitList第522行.

情况是,它认为十进制数字6.0289已经四舍五入(与等效BigDecimal表示相比,这是正确的6.208899…)并且决定不再向上舍入.问题是这个决定只有在四舍五入产生的数字的情况下才有意义5,而不是在它大于的情况下5.请注意代码如何HALF_DOWN正确区分digit=='5'digit>'5'案例.

显然,这是一个错误,并且是一个奇怪的错误,因为执行类似权限的代码(仅针对另一个方向)正好在破坏的代码之下.

        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;
Run Code Online (Sandbox Code Playgroud)

这不会发生在另一个数字上的原因是,这6.2088不是四舍五入的结果(再次,与BigDecimal输出相比6.208800…).因此,在这种情况下,它将向上舍入.

  • 如果有人想知道为什么他们确实改变了算法:Java 7对于诸如`1234567890123.45949`之类的值做错了.因此修复程序引入了一个新的bug ...解决方法是使用`nf.format(new BigDecimal(num))`来避免这两个错误. (4认同)
  • @Peter Lawrey:`new BigDecimal(double)`和`BigDecimal.valueOf(double)`不一样.后者将执行隐式的round-to-double操作,这将重新引入`1234567890123.45949`的双舍入问题.但是,如果你真的意味着`1234567890123.4595`,`valueOf`将是更好的选择.如果你想要安全起见,请使用`BigDecimal.valueOf(String)` (3认同)
  • +1表示已经为HALF_DOWN修复了它. (2认同)