为什么Math.round(0.49999999999999994)返回1
在下面的程序中,您可以看到,对于每个稍微小于.5
值,向下取整,除了0.5
。
for (int i = 10; i >= 0; i--) { long l = Double.doubleToLongBits(i + 0.5); double x; do { x = Double.longBitsToDouble(l); System.out.println(x + " rounded is " + Math.round(x)); l--; } while (Math.round(x) > i); }
版画
10.5 rounded is 11 10.499999999999998 rounded is 10 9.5 rounded is 10 9.499999999999998 rounded is 9 8.5 rounded is 9 8.499999999999998 rounded is 8 7.5 rounded is 8 7.499999999999999 rounded is 7 6.5 rounded is 7 6.499999999999999 rounded is 6 5.5 rounded is 6 5.499999999999999 rounded is 5 4.5 rounded is 5 4.499999999999999 rounded is 4 3.5 rounded is 4 3.4999999999999996 rounded is 3 2.5 rounded is 3 2.4999999999999996 rounded is 2 1.5 rounded is 2 1.4999999999999998 rounded is 1 0.5 rounded is 1 0.49999999999999994 rounded is 1 0.4999999999999999 rounded is 0
我正在使用Java 6更新31。
概要
在Java 6中(可能更早), round(x)
被实现为floor(x+0.5)
。 1这是一个规范错误,正是这个病态的情况。 2 Java 7不再要求这个破坏的实现。 3
问题
0.5 + 0.49999999999999994正好是双精度1:
static void print(double d) { System.out.printf("%016x\n", Double.doubleToLongBits(d)); } public static void main(String args[]) { double a = 0.5; double b = 0.49999999999999994; print(a); // 3fe0000000000000 print(b); // 3fdfffffffffffff print(a+b); // 3ff0000000000000 print(1.0); // 3ff0000000000000 }
这是因为0.49999999999999994的指数小于0.5,所以当它们被添加时,它的尾数被移位,并且ULP变大。
解决scheme
从Java 7开始,OpenJDK(例如)就这样实现它: 4
public static long round(double a) { if (a != 0x1.fffffffffffffp-2) // greatest double value less than 0.5 return (long)floor(a + 0.5d); else return 0; }
1. http://docs.oracle.com/javase/6/docs/api/java/lang/Math.html#round%28double%29
2. http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6430675 (信贷给@SimonNickersonfind这个)
http://docs.oracle.com/javase/7/docs/api/java/lang/Math.html#round%28double%29
这似乎是一个已知的bug( Java bug 6430675:Math.round对于0x1.fffffffffffffp-2具有令人惊讶的行为 ),这已经在Java 7中修复了。
JDK 6中的源代码
public static long round(double a) { return (long)Math.floor(a + 0.5d); }
源代码在JDK 7
public static long round(double a) { if (a != 0x1.fffffffffffffp-2) { // a is not the greatest double value less than 0.5 return (long)Math.floor(a + 0.5d); } else { return 0; } }
当值为0.49999999999999994d时,在JDK 6中,它将调用floor并因此返回1,但在JDK 7中,if条件是检查该数是否是最大double值小于0.5。 在这种情况下,数字不是小于0.5的最大double值,所以else块返回0。
你可以尝试0.49999999999999999d,这将返回1但不是0,因为这是最大的double值小于0.5。
我已经在jdk 1.6 32bit上得到了相同的结果,但是在java 7 64bit上,我已经去掉0.49999999999999994的值了,舍入为0,最后一行不打印。 这似乎是虚拟机问题,但是,使用浮点数,您应该期望在各种环境(CPU,32位或64位模式)上的结果有所不同,
而且,当使用round
或反转matrix等时,这些位可以产生巨大的差异。
x64输出:
10.5 rounded is 11 10.499999999999998 rounded is 10 9.5 rounded is 10 9.499999999999998 rounded is 9 8.5 rounded is 9 8.499999999999998 rounded is 8 7.5 rounded is 8 7.499999999999999 rounded is 7 6.5 rounded is 7 6.499999999999999 rounded is 6 5.5 rounded is 6 5.499999999999999 rounded is 5 4.5 rounded is 5 4.499999999999999 rounded is 4 3.5 rounded is 4 3.4999999999999996 rounded is 3 2.5 rounded is 3 2.4999999999999996 rounded is 2 1.5 rounded is 2 1.4999999999999998 rounded is 1 0.5 rounded is 1 0.49999999999999994 rounded is 0
以下答案是http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6430675上的一个oracle错误报告的摘录。; 访问链接的完整说明。
方法{Math,StrictMath.round在操作上定义为
(long)Math.floor(a + 0.5d)
双重论点。 虽然这个定义通常按照预期工作,但是对于0x1.fffffffffffffp-2(0.49999999999999994),却给出了令人惊讶的结果1,而不是0。
值0.49999999999999994是小于0.5的最大浮点值。 作为hex浮点数字,其值为0x1.fffffffffffffp-2,等于(2 – 2 ^ 52)* 2 ^ -2。 ==(0.5 – 2 ^ 54)。 因此,总和的确切值
(0.5 - 2^54) + 0.5
是1 – 2 ^ 54。 这是两个相邻浮点数(1 – 2 ^ 53)和1之间的一半。在IEEE 754algorithm轮到Java使用的最接近的舍入模式中,当浮点结果不精确时,必须返回包含确切结果的可表示的浮点值; 如果两个值相等,则返回最后一位为零的那个值。 在这种情况下,来自add的正确返回值是1,而不是小于1的最大值。
虽然该方法按照定义运行,但这种input的行为是非常令人惊讶的。 这个规范可以被定义为更接近于“最接近最长,四舍五入关系”的东西,这样就可以改变这个input的行为。