即使显式设置,DecimalFormat 使用不正确的 RoundingMode

DecimalFormat using incorrect RoundingMode even when set explicitly

为什么这个 DecimalFormat 没有像预期的那样使用 RoundingMode.HALF_UP 舍入,应该怎样做才能得到预期的输出?用DecimalFormat可以吗?

DecimalFormat df = new DecimalFormat("0.0");

df.setRoundingMode(RoundingMode.HALF_UP);

df.format(0.05); // expecting 0.1, getting 0.1
df.format(0.15); // expecting 0.2, getting 0.1  << unexpected

df.format(0.06); // expecting 0.1, getting 0.0  << unexpected

我看过 this question, specifically this answer 的答案,但它似乎只在四舍五入为整数时才有效。

我的JDK是8.0.110,我的JRE是8.0.730.2

(使用 Java 8 回答下面的问题。)

您看到的问题来自于在代码中指定“0.15”或“0.05”,当用双精度表示时,它略小于 0.15。看看这个

DecimalFormat df = new DecimalFormat("#.#");

df.setRoundingMode(RoundingMode.HALF_UP);

BigDecimal bd = new BigDecimal(0.15);
System.out.println("bd=" + bd);
System.out.println(df.format(0.15)); // expecting 0.1, getting 0.1
bd = new BigDecimal(0.05);
System.out.println("bd=" + bd);
System.out.println(df.format(0.05));
bd = new BigDecimal(0.06);
System.out.println("bd=" + bd);
System.out.println(df.format(0.06));

这段代码的输出是

bd=0.1499999999999999944488848768742172978818416595458984375
0.1
bd=0.05000000000000000277555756156289135105907917022705078125
0.1
bd=0.059999999999999997779553950749686919152736663818359375
0.1

一个可能的解决方案(如果你绝对需要它以正确的方式四舍五入)是使用 BigDecimal.valueOf 来创建值。例如

BigDecimal bd = BigDecimal.valueOf(0.15);
System.out.println("bd=" + bd);
System.out.println(df.format(bd)); // expecting 0.1, getting 0.1
bd = BigDecimal.valueOf(0.05);
System.out.println("bd=" + bd);
System.out.println(df.format(bd));
bd = BigDecimal.valueOf(0.06);
System.out.println("bd=" + bd);
System.out.println(df.format(bd));

现在将产生

bd=0.15
0.2
bd=0.05
0.1
bd=0.06
0.1

顺便说一句,正如 Scary Wombat 指出的那样,掩码设置为 0.0 而不是 #.# 将使 0.6 0。但我认为这是我开始查看时的后期编辑。使用#.#.

当你这样做时

df.format(0.15);

实际上有两个舍入操作。最明显的是你用 df.format 要求的那个,但还有一个更早的是在编译时发生的。

十进制值 0.15 不能表示为双精度值。双精度只能表示分母是2的幂的有理数,就像十进制只能表示分母是10的幂的有理数一样。当您编写 0.15 时,编译器将其四舍五入为最接近的可表示为双精度的值,而该值恰好是

0.1499999999999999944488848768742172978818416595458984375

其中 df.format 正确向下舍入。它不正好在 0.1 和 0.2 之间,所以 HALF_UP 舍入模式无关紧要。

至于

df.format(0.06); // expecting 0.1, getting 0.0  << unexpected

如果您看到 那个,那是一个错误。它看起来与使用 8u51 的 one linked by ScaryWombat, reported to be fixed in 8u40 and 8u45. A test on Ideone 匹配,显示了正确的行为。更新您的 Java 应该可以解决问题。

我认为答案是 IEEE Standard for Floating-Point Arithmetic (IEEE 754). The Wiki article has a good example in the Rounding rules section. Also you can read section 9.1 of Introduction to Java that expends more about floating point. BigDecimal 通过将未调用的值存储在 BigInteger 中来解决大部分这些缺点,精度和小数位数在整数字段中分开。