總覽
錯誤表示錯誤和算術舍入錯誤有兩種類型,它們在浮點計算中很常見。 在此簡單示例中,這兩個錯誤組合在一起,在Java 6中Math.round(0.4999999999999999999917)舍入為1。
表示錯誤
浮點數是以2為底的格式,表示所有數字都表示為2的冪的和。例如6.25是2 ^ 2 + 2 ^ 1 + 2 ^ -2。 但是,即使像0.1這樣的簡單數字也無法準確表示。 轉換為BigDecimal時,這一點變得很明顯,因為它將保留實際表示的值而無需取整。
new BigDecimal(0.1)= 0.1000000000000000055511151231257827021181583404541015625
BigDecimal.valueOf(0.1)= 0.1
使用構造函數獲取實際表示的值,使用valueOf給出與打印雙 精度字相同的舍入值
解析數字時,會將其舍入為最接近的表示值。 這意味著存在一個略小于0.5的數字,由于它是最接近的表示值,因此將四舍五入為0.5。
下面用蠻力搜索舍入為1.0的最小值
public static final BigDecimal TWO = BigDecimal.valueOf(2);public static void main(String... args) {int digits = 80;BigDecimal low = BigDecimal.ZERO;BigDecimal high = BigDecimal.ONE;for (int i = 0; i <= 10 * digits / 3; i++) {BigDecimal mid = low.add(high).divide(TWO, digits, RoundingMode.HALF_UP);if (mid.equals(low) || mid.equals(high))break;if (Math.round(Double.parseDouble(mid.toString())) > 0)high = mid;elselow = mid;}System.out.println("Math.round(" + low + ") is " + Math.round(Double.parseDouble(low.toString())));System.out.println("Math.round(" + high + ") is " + Math.round(Double.parseDouble(high.toString())));
}
源代碼
在Java 7上,您得到以下結果。
Math.round(0.49999999999999997224442438437108648940920829772949218749999999999999999999999999) is 0
Math.round(0.49999999999999997224442438437108648940920829772949218750000000000000000000000000) is 1
令人驚訝的是,在Java 6中,您獲得了關注。
Math.round(0.49999999999999991673327315311325946822762489318847656250000000000000000000000000) is 0
Math.round(0.49999999999999991673327315311325946822762489318847656250000000000000000000000001) is 1
這些數字從何而來?
Java 7值是0.5和前一個表示值之間的中點。 高于此中點時,解析時該值將舍入為0.5。
Java 6值是0.5之前的值與其之前的值之間的中點。
Value 0.5 is 0.5
The previous value is 0.499999999999999944488848768742172978818416595458984375
... and the previous is 0.49999999999999988897769753748434595763683319091796875The mid point between 0.5and 0.499999999999999944488848768742172978818416595458984375is 0.4999999999999999722444243843710864894092082977294921875... and the mid point between 0.499999999999999944488848768742172978818416595458984375and 0.49999999999999988897769753748434595763683319091796875is 0.4999999999999999167332731531132594682276248931884765625
為什么Java 6的值更小
在Java 6 Javadoc中, Math.round(double)被定義為
(long)Math.floor(a + 0.5d)
此定義的問題在于0.49999999999999994 + 0.5的舍入誤差為1.0。
在Java 7 Javadoc Math.round(double)中,它僅聲明:
返回最接近參數的長整數,并舍入四舍五入。
那么Java 7如何解決這個問題?
Java 7的Math.round的源代碼如下所示
public static long round(double a) {if (a != 0x1.fffffffffffffp-2) // greatest double value less than 0.5return (long)floor(a + 0.5d);elsereturn 0;
}
最大值小于0.5的結果將進行硬編碼。
那么0x1.fffffffffffffp-2是什么呢?
它是浮點值的十六進制表示。 它很少使用,但是它很精確,因為所有值都可以無錯誤地表示(最多53位)。
相關鏈接
錯誤ID:6430675 Math.round對于0x1.fffffffffffffpp-2具有令人驚訝的行為
為什么Math.round(0.49999999999999994)返回1
參考: 為什么在Java 6上 ,我們的JCG合作伙伴 Peter Lawrey在Vanilla Java博客上將Math.round(0.499999999999999917)舍入為1 。
翻譯自: https://www.javacodegeeks.com/2012/04/why-mathround0499999999999999917-rounds.html