一个JDK7的四舍五入的bug引发的思考 ## 1.背景: 今天我的 [feilong-core](https://github.com/venusdrogon/feilong-core "feilong-core") 项目使用 `jdk8` 进行`maven install` 的时候,有一个测试类报错, 但是原先使用`jdk7` 进行`maven install`的时候却是正常通过, issue 参见 [https://github.com/venusdrogon/feilong-core/issues/165](https://github.com/venusdrogon/feilong-core/issues/165) ## 2.测试类代码如下: ```JAVA @Test public void testFormat32(){ assertEquals("1.2", NumberFormatUtil.format(1.15, "#####.#", RoundingMode.HALF_EVEN)); assertEquals("1.2", NumberFormatUtil.format(1.25, "#####.#", RoundingMode.HALF_EVEN)); assertEquals("1.3", NumberFormatUtil.format(1.251, "#####.#", RoundingMode.HALF_EVEN)); assertEquals("-1.2", NumberFormatUtil.format(-1.15, "#####.#", RoundingMode.HALF_EVEN)); assertEquals("-1.2", NumberFormatUtil.format(-1.25, "#####.#", RoundingMode.HALF_EVEN)); assertEquals("-1.3", NumberFormatUtil.format(-1.251, "#####.#", RoundingMode.HALF_EVEN)); } @Test public void testFormat321(){ assertEquals("1.2", NumberFormatUtil.format(1.15, "#####.#", null)); assertEquals("1.3", NumberFormatUtil.format(1.25, "#####.#", null)); assertEquals("1.3", NumberFormatUtil.format(1.251, "#####.#", null)); assertEquals("-1.2", NumberFormatUtil.format(-1.15, "#####.#", null)); assertEquals("-1.3", NumberFormatUtil.format(-1.25, "#####.#", null)); assertEquals("-1.3", NumberFormatUtil.format(-1.251, "#####.#", null)); } @Test public void testFormat111(){ assertEquals("1.2", NumberFormatUtil.format(1.15, "#####.#")); assertEquals("1.3", NumberFormatUtil.format(1.25, "#####.#")); assertEquals("1.3", NumberFormatUtil.format(1.251, "#####.#")); assertEquals("-1.2", NumberFormatUtil.format(-1.15, "#####.#")); assertEquals("-1.3", NumberFormatUtil.format(-1.25, "#####.#")); assertEquals("-1.3", NumberFormatUtil.format(-1.251, "#####.#")); } ``` ## 3.报错信息 ```XML Tests run: 568, Failures: 3, Errors: 0, Skipped: 2, Time elapsed: 2.194 sec <<< FAILURE! - in com.feilong.core.FeiLongSuiteTests testFormat111(com.feilong.core.text.NumberFormatUtilTest) Time elapsed: 0.009 sec <<< FAILURE! org.junit.ComparisonFailure: expected:<1.[2]> but was:<1.[1]> at com.feilong.core.text.NumberFormatUtilTest.testFormat111(NumberFormatUtilTest.java:149) testFormat321(com.feilong.core.text.NumberFormatUtilTest) Time elapsed: 0 sec <<< FAILURE! org.junit.ComparisonFailure: expected:<1.[2]> but was:<1.[1]> at com.feilong.core.text.NumberFormatUtilTest.testFormat321(NumberFormatUtilTest.java:135) testFormat32(com.feilong.core.text.NumberFormatUtilTest) Time elapsed: 0.001 sec <<< FAILURE! org.junit.ComparisonFailure: expected:<1.[2]> but was:<1.[1]> at com.feilong.core.text.NumberFormatUtilTest.testFormat32(NumberFormatUtilTest.java:121) Results : Failed tests: NumberFormatUtilTest.testFormat111:149 expected:<1.[2]> but was:<1.[1]> NumberFormatUtilTest.testFormat32:121 expected:<1.[2]> but was:<1.[1]> NumberFormatUtilTest.testFormat321:135 expected:<1.[2]> but was:<1.[1]> Tests run: 568, Failures: 3, Errors: 0, Skipped: 2 [INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE ``` ## 4.原因 经过搜索,发现 - https://bugs.openjdk.java.net/browse/JDK-7131459 - https://bugs.openjdk.java.net/browse/JDK-8029896 - http://stackoverflow.com/questions/22797964/is-inconsistency-in-rounding-between-java-7-and-java-8-a-bug `jdk7 `上是bug , `jdk8` 修复了 ![image](https://cloud.githubusercontent.com/assets/3479472/17660670/3cadd984-630d-11e6-9d31-747d610ef110.png) ## 5.思考 - 不要使用 `float` 或者 `double` 浮点数来表述货币这样的精确数量的值,会导致舍入误差。使用浮点数来进行元和分计算会得到**灾难性的后果**。 - 浮点数最好用来表示象测量值这类数值,这类值从一开始就不怎么精确。(PS:一般情况在你的工作中理论上是用不到的) - 尽量不要用 `float` 或者 `double` ,来做 +-*/ 运算,以及format,小数请使用 `BigDecimal` - `BigDecimal` 的构造函数有 - `java.math.BigDecimal.BigDecimal(double)` - `java.math.BigDecimal.BigDecimal(String)` 请使用 `java.math.BigDecimal.BigDecimal(String)`)(PS:如果你使用 `sonar` 进行代码扫描的话,它会给你提示和建议) - 比较两个 `BigDecimal` 大小,请使用 `java.math.BigDecimal.compareTo(BigDecimal)` 方法,而**不要**使用 `java.math.BigDecimal.equals(Object)`方法, 因为 > equals()方法认为,两个表示同一个数但换算值不同(例如, 100.00 和 100.000 )的 BigDecimal 值是不相等的。 > 然而, compareTo() 方法会认为这两个数是相等的,所以在从数值上比较两个 BigDecimal 值时,应该使用 compareTo() 而不是 equals() 。 **参见源码:** ![](http://i.imgur.com/uT03nUf.png) ## 6.参考 - https://bugs.openjdk.java.net/browse/JDK-7131459 - https://bugs.openjdk.java.net/browse/JDK-8029896 - http://stackoverflow.com/questions/22797964/is-inconsistency-in-rounding-between-java-7-and-java-8-a-bug - http://www.ibm.com/developerworks/cn/java/j-jtp0114/index.html#4