总览
错误表示错误和算术舍入错误有两种类型,它们在浮点计算中很常见。 在此简单示例中,这两个错误组合在一起,在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