一个JDK7的四舍五入的bug引发的思考
1.背景:
今天我的 feilong-core 项目使用 jdk8
进行maven install
的时候,有一个测试类报错, 但是原先使用jdk7
进行maven install
的时候却是正常通过,
issue 参见 venusdrogon/feilong-core#165
2.测试类代码如下:
@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.报错信息
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
修复了
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() 。参见源码: