|
| 1 | +# 为什么数学函数Math.round(0.49999999999999994) 返回 1 |
| 2 | + |
| 3 | +tags:stackoverflow-java-top-qa |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +###问题 |
| 8 | +通过下面的程序你可以看出来,对于任意一个比0.5略小的都是舍去小数向下取整,只有0.5是例外. |
| 9 | + |
| 10 | +```java |
| 11 | +for (int i = 10; i >= 0; i--) { |
| 12 | + long l = Double.doubleToLongBits(i + 0.5); |
| 13 | + double x; |
| 14 | + do { |
| 15 | + x = Double.longBitsToDouble(l); |
| 16 | + System.out.println(x + " rounded is " + Math.round(x)); |
| 17 | + l--; |
| 18 | + } while (Math.round(x) > i); |
| 19 | +} |
| 20 | +``` |
| 21 | + |
| 22 | +输出为: |
| 23 | + |
| 24 | +``` |
| 25 | +10.5 rounded is 11 |
| 26 | +10.499999999999998 rounded is 10 |
| 27 | +9.5 rounded is 10 |
| 28 | +9.499999999999998 rounded is 9 |
| 29 | +8.5 rounded is 9 |
| 30 | +8.499999999999998 rounded is 8 |
| 31 | +7.5 rounded is 8 |
| 32 | +7.499999999999999 rounded is 7 |
| 33 | +6.5 rounded is 7 |
| 34 | +6.499999999999999 rounded is 6 |
| 35 | +5.5 rounded is 6 |
| 36 | +5.499999999999999 rounded is 5 |
| 37 | +4.5 rounded is 5 |
| 38 | +4.499999999999999 rounded is 4 |
| 39 | +3.5 rounded is 4 |
| 40 | +3.4999999999999996 rounded is 3 |
| 41 | +2.5 rounded is 3 |
| 42 | +2.4999999999999996 rounded is 2 |
| 43 | +1.5 rounded is 2 |
| 44 | +1.4999999999999998 rounded is 1 |
| 45 | +0.5 rounded is 1 |
| 46 | +0.49999999999999994 rounded is 1 |
| 47 | +0.4999999999999999 rounded is 0 |
| 48 | +
|
| 49 | +``` |
| 50 | +*_译者注:请看输出的最后两行,0.49999999999999994的输出为1,而0.49999999999999999的输出为0* |
| 51 | + |
| 52 | +我使用的版本是 Java 6 update 31 |
| 53 | + |
| 54 | +### 回答 |
| 55 | +**总结** |
| 56 | + |
| 57 | +在 Java 6(或者之前的版本),round(x)是用floor(x+0.5)实现的.¹ 这是一个规范上的bug,恰恰是在这种病理条件下.²Java 7 不再使用这个有问题的实现了. |
| 58 | + |
| 59 | +**问题** |
| 60 | + |
| 61 | +0.5+0.49999999999999994 在double的精度下的结果是1 |
| 62 | +```java |
| 63 | +static void print(double d) { |
| 64 | + System.out.printf("%016x\n", Double.doubleToLongBits(d)); |
| 65 | +} |
| 66 | + |
| 67 | +public static void main(String args[]) { |
| 68 | + double a = 0.5; |
| 69 | + double b = 0.49999999999999994; |
| 70 | + |
| 71 | + print(a); // 3fe0000000000000 |
| 72 | + print(b); // 3fdfffffffffffff |
| 73 | + print(a+b); // 3ff0000000000000 |
| 74 | + print(1.0); // 3ff0000000000000 |
| 75 | +} |
| 76 | +``` |
| 77 | +这是因为0.49999999999999994的指数比0.5的指数小,所以当它们两个相加时,0.49999999999999994的原数就会发生移位,然后最小精度单位(unit of least precision)/最后置单位(unit of last place)相应的变大了. |
| 78 | + |
| 79 | +**解决方案** |
| 80 | + |
| 81 | +自从Java 7以来,OpenJDK(举个栗子)实现如下⁴: |
| 82 | + |
| 83 | +```java |
| 84 | +public static long round(double a) { |
| 85 | + if (a != 0x1.fffffffffffffp-2) // greatest double value less than 0.5 |
| 86 | + return (long)floor(a + 0.5d); |
| 87 | + else |
| 88 | + return 0; |
| 89 | +} |
| 90 | +``` |
| 91 | +1. [http://docs.oracle.com/javase/6/docs/api/java/lang/Math.html#round%28double%29](http://docs.oracle.com/javase/6/docs/api/java/lang/Math.html#round%28double%29) |
| 92 | +2. [http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6430675](http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6430675) (credits to @SimonNickerson for finding this) |
| 93 | +3. [http://docs.oracle.com/javase/7/docs/api/java/lang/Math.html#round%28double%29](http://docs.oracle.com/javase/7/docs/api/java/lang/Math.html#round%28double%29) |
| 94 | +4. [http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/java/lang/Math.java#Math.round%28double%29](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/java/lang/Math.java#Math.round%28double%29) |
| 95 | + |
| 96 | +### stackoverflow原文链接:[http://stackoverflow.com/questions/9902968/why-does-math-round0-49999999999999994-return-1](http://stackoverflow.com/questions/9902968/why-does-math-round0-49999999999999994-return-1) |
0 commit comments