即使显式设置,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 中来解决大部分这些缺点,精度和小数位数在整数字段中分开。
为什么这个 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 中来解决大部分这些缺点,精度和小数位数在整数字段中分开。