当前位置: 首页 > 知识库问答 >
问题:

NumberFormat四舍五入问题,仅Java8

姬昀
2023-03-14

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

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

给出以下输出?

[me@localhost trunk]$ java Test
BigDecimal: 6.208800000000000096633812063373625278472900390625
Formatted: 6.209
BigDecimal: 6.208899999999999863575794734060764312744140625
Formatted: 6.208

以防您没有看到:“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)

编辑:这是Java7的输出:

[me@localhost trunk]$ java Test
BigDecimal: 6.208800000000000096633812063373625278472900390625
Formatted: 6.209
BigDecimal: 6.208899999999999863575794734060764312744140625
Formatted: 6.209

Java7版本:

[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)

共有3个答案

东方志尚
2023-03-14

通过代码追踪到DigitList.set

final void set(boolean isNegative, double source, int maximumDigits, boolean fixedPoint) {

    FloatingDecimal.BinaryToASCIIConverter fdConverter  = FloatingDecimal.getBinaryToASCIIConverter(source);
    boolean hasBeenRoundedUp = fdConverter.digitsRoundedUp();

我对这个bug有一个更简单的测试

import java.math.RoundingMode;
import java.text.NumberFormat;
import java.util.Locale;

public class Test {
    public static void main(String... args) {
        for (int i = 0; i < 100; i++)
            test(i / 100.0);
    }

    private static void test(double num) {
        NumberFormat nf = NumberFormat.getInstance(Locale.ENGLISH);
        nf.setMaximumFractionDigits(1);
        String round1 = nf.format(num);

        NumberFormat nf2 = NumberFormat.getInstance(Locale.ENGLISH);
        nf2.setMaximumFractionDigits(1);
        nf2.setRoundingMode(RoundingMode.HALF_UP);
        String round2 = nf2.format(num);
        if (!round1.equals(round2))
            System.out.printf("%s, formatted with HALF_UP was %s but should be %s%n", num, round2, round1);
    }
}

印刷品

0.06, formatted with HALF_UP was 0 but should be 0.1
0.09, formatted with HALF_UP was 0 but should be 0.1
0.18, formatted with HALF_UP was 0.1 but should be 0.2
0.25, formatted with HALF_UP was 0.3 but should be 0.2
0.29, formatted with HALF_UP was 0.2 but should be 0.3
0.36, formatted with HALF_UP was 0.3 but should be 0.4
0.37, formatted with HALF_UP was 0.3 but should be 0.4
0.47, formatted with HALF_UP was 0.4 but should be 0.5
0.48, formatted with HALF_UP was 0.4 but should be 0.5
0.49, formatted with HALF_UP was 0.4 but should be 0.5
0.57, formatted with HALF_UP was 0.5 but should be 0.6
0.58, formatted with HALF_UP was 0.5 but should be 0.6
0.59, formatted with HALF_UP was 0.5 but should be 0.6
0.69, formatted with HALF_UP was 0.6 but should be 0.7
0.86, formatted with HALF_UP was 0.8 but should be 0.9
0.87, formatted with HALF_UP was 0.8 but should be 0.9
0.96, formatted with HALF_UP was 0.9 but should be 1
0.97, formatted with HALF_UP was 0.9 but should be 1
0.98, formatted with HALF_UP was 0.9 but should be 1
0.99, formatted with HALF_UP was 0.9 but should be 1

在不正确的情况下hasBeenRoundedUp为true,这可以防止任何进一步的舍入。请注意,如果您放弃设置舍入,它有一个正确舍入的默认路径。

我不会使用数字格式。它的使用非常缓慢和复杂。

import java.math.BigDecimal;

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) {
        BigDecimal bd = new BigDecimal(num);
        BigDecimal bd2 = BigDecimal.valueOf(num);
        System.out.println("new BigDecimal: " + bd);
        System.out.println("BigDecimal.valueOf: " + bd2);
        System.out.printf("%." + numDecimal + "f%n", num);
        System.out.printf("%." + numDecimal + "f%n", bd);
        System.out.printf("%." + numDecimal + "f%n", bd2);
        System.out.printf("%f%n", round3(num));
        System.out.printf("%s%n", round3(num));
        System.out.printf("%f%n", bd.setScale(numDecimal, BigDecimal.ROUND_HALF_UP));
        System.out.printf("%s%n", bd.setScale(numDecimal, BigDecimal.ROUND_HALF_UP));
        System.out.printf("%f%n", bd2.setScale(numDecimal, BigDecimal.ROUND_HALF_UP));
        System.out.printf("%s%n", bd2.setScale(numDecimal, BigDecimal.ROUND_HALF_UP));
    }

    private static double round3(double num) {
        final double factor = 1e3;
        return Math.round(num * factor) / factor;
    }
}

使用Java 8打印。

new BigDecimal: 6.208800000000000096633812063373625278472900390625
BigDecimal.valueOf: 6.2088
6.209
6.209
6.209
6.209000
6.209
6.209000
6.209
6.209000
6.209
new BigDecimal: 6.208899999999999863575794734060764312744140625
BigDecimal.valueOf: 6.2089
6.209
6.209
6.209
6.209000
6.209
6.209000
6.209
6.209000
6.209
何章横
2023-03-14
  • Bug 8039915跟踪Java9的OpenJDK修复程序
  • Bug 8061380将反向端口跟踪到OpenJDK 8
  • 带有JDK的8u40公开GA版本1.8.0_40-b25于2015年3月3日发布(发行说明)

感谢Holger回答中的研究,我能够开发一个运行时补丁,并且我的雇主已经根据GPLv2许可条款免费发布了它,其中包含Classpath Exception1(与OpenJDK源代码相同)。

补丁项目和源代码托管在GitHub上,包含有关此bug的更多详细信息以及可下载二进制文件的链接。该补丁不对磁盘上安装的Java文件进行任何修改,并且应该可以安全地在所有Oracle Java版本上使用

当修补程序检测到字节码签名表明存在缺陷时,它会用修改后的实现来替换开关案例:

if (digits[maximumDigits] > '5') {
    return true;
} else if (digits[maximumDigits] == '5') {
    return maximumDigits != (count - 1)
        || allDecimalDigits
        || !alreadyRounded;
}
// else
return false; // in original switch(), was: break;

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

贺兴平
2023-03-14

我可以把这个问题追溯到java类。文本数字列表第522行。

情况是,它认为十进制数字已四舍五入(与等效的BigDecimal表示法相比,这是正确的…)并决定不再四舍五入。问题是,这个决定只有在四舍五入产生的数字是5的情况下才有意义,而不是当它大于5的情况下才有意义。请注意下半部分的代码如何正确区分数字==“5”和数字

显然,这是一个bug,而且考虑到这样一个事实,即正确执行类似操作的代码(仅针对另一个方向)就在错误代码的正下方,这是一个奇怪的bug。

        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;

另一个数字没有发生这种情况的原因是6.2088不是四舍五入的结果(再次,与BigDecimal输出6.208800...)。所以在这种情况下,它会四舍五入。

 类似资料:
  • 问题内容: 有人可以向我解释以下代码的原因: 给出以下输出? 如果您看不到它,则:“ 6.2089”舍入为3位数字将输出为“ 6.208”,而“ 6.2088”将输出为“ 6.209”。少即是多? 使用Java 5、6或7时,效果很好,但是此Java 8给了我奇怪的输出。Java版本: 编辑:这是Java 7的输出: Java 7版本: 问题答案: 我可以将此问题归类到522行。 这种情况是,它认

  • 本节讨论了精度数学的四舍五入特性,ROUND()函数,以及插入DECIMAL列时的四舍五入特性。 ROUND()函数的行为取决于其参量是准确的还是近似的: ·对于准确值数值,ROUND()采用“半值向上舍入”规则:如果小数部分的值为.5或更大,如果是正数,向上取下一个整数,如果是负数,向下取下一个整数(换句话讲,以0为界限执行舍入)。如果小数部分的值小于.5,如果是正数,向下取下一个整数,如果是负

  • 问题内容: 据我所知,当四舍五入为5时,四舍五入应按以下方式进行。 如果我们做所有的总结, 我很确定在.Net中也可以进行这种四舍五入。 但是在SQL Server中,总会四舍五入,这在数学上是不正确的。 这导致四舍五入后的总值相差1美分。 我已经尝试过使用十进制和货币数据类型。 难道我做错了什么?有没有解决的办法? 问题答案: 如果您确实想在SQL Server中使用银行取整…

  • 问题内容: 我怎么总是将a舍入为一个,而不舍入它。我知道,但是我希望它总是四舍五入。因此,如果为,则四舍五入为4。 问题答案: 您可以使用方法。 请参阅JavaDoc链接:https ://docs.oracle.com/javase/10/docs/api/java/lang/Math.html#ceil(double ) 从文档: 细胞 返回大于或等于自变量且等于数学整数的最小(最接近负无穷大

  • 有人能告诉我怎么打双人球吗 e、 g.从双倍值55.6666666666667开始-四舍五入到双倍值56.0000000000- 或者从55.333333333333开始-四舍五入到55.0000000000的两倍- 谢谢。

  • 问题 你想对浮点数执行指定精度的舍入运算。 解决方案 对于简单的舍入运算,使用内置的 round(value, ndigits) 函数即可。比如: >>> round(1.23, 1) 1.2 >>> round(1.27, 1) 1.3 >>> round(-1.27, 1) -1.3 >>> round(1.25361,3) 1.254 >>> 当一个值刚好在两个边界的中间的时候, round