Java8中的RoundingMode.HALF_DOWN问题

我使用的是jdk 1.8.0_45,我们的测试发现了一个错误。 当决定舍入的最后一个小数为5时,RoundingMode.HALF_DOWN与RoundingMode.HALF_UP的工作方式相同。

我发现了RoundingMode.HALF_UP的相关问题,但它们在更新40中得到修复。我还在oracle中添加了一个bug,但根据我的经验,它们确实没有响应。

package testjava8; import java.math.RoundingMode; import java.text.DecimalFormat; public class Formatori { public static void main(String[] args) { DecimalFormat format = new DecimalFormat("#,##0.0000"); format.setRoundingMode(RoundingMode.HALF_DOWN); Double toFormat = 10.55555; System.out.println("Round down"); System.out.println(format.format(toFormat)); format.setRoundingMode(RoundingMode.HALF_UP); toFormat = 10.55555; System.out.println("Round up"); System.out.println(format.format(toFormat)); } } 

实际结果:向下舍入10.5556向上舍入10.5556

预期结果(以jdk 1.7获得):向下舍入10.5555向上舍入10.5556

似乎它是有意改变的。 JDK 1.7行为不正确。

问题是你根本无法使用double类型表示数字10.55555 。 它以IEEE二进制格式存储数据,因此当您将十进制10.55555数分配给double变量时,实际上可以得到最接近的值,可以用IEEE格式表示: 10.555550000000000210320649784989655017852783203125 。 此数字大于10.55555 ,因此在HALF_DOWN模式下正确舍入到10.5556

您可以检查一些可以用二进制精确表示的数字。 例如, 10.1562510 + 5/32 ,因此二进制为1010.00101 )。 此数字在HALF_DOWN模式下舍入为10.1562HALF_DOWN模式下舍入为10.1562

如果要恢复旧行为,可以先使用BigDecimal.valueOf构造函数将数字转换为BigDecimal ,“使用double的规范字符串表示forms将double转换为BigDecimal ”:

 BigDecimal toFormat = BigDecimal.valueOf(10.55555); System.out.println("Round down"); System.out.println(format.format(toFormat)); // 10.5555 format.setRoundingMode(RoundingMode.HALF_UP); toFormat = BigDecimal.valueOf(10.55555); System.out.println("Round up"); System.out.println(format.format(toFormat)); // 10.5556 

行为的更改记录在Java 8的发行说明中

使用NumberFormatDecimalFormat类时,在某些极端情况下,以前版本的JDK的舍入行为是错误的。 […]

例如,当使用默认的建议NumberFormatFormat APIforms: NumberFormat nf = java.text.NumberFormat.getInstance()后跟nf.format(0.8055d) ,值0.8055d在计算机中记录为0.8054999999999999378275106209912337362766265869140625,因为此值不能完全用二进制格式表示。 这里,默认的舍入规则是“half-even”,而JDK 7中调用format()的结果是“0.806”的错误输出,而正确的结果是“0.805”,因为记录在内存中的值是电脑是“低于”领带。

对于可能由程序员选择的任何模式定义的所有舍入位置(非默认模式),也实现了这种新行为。