Why does Math.round(0.49999999999999994) return 1?
Почему Math.round(0.49999999999999994) возвращает 1?
В следующей программе вы можете видеть, что каждое значение, чуть меньшее .5, округляется в меньшую сторону, за исключением 0.5.
for (inti=10; i >= 0; i--) { longl= 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.
Переведено автоматически
Ответ 1
Краткие сведения
В Java 6 (и, предположительно, более ранних версиях), round(x) реализовано как floor(x+0.5).1 Это ошибка спецификации именно для этого патологического случая.2 Java 7 больше не требует этой неработающей реализации.3
Проблема
0.5+0.49999999999999994 - это ровно 1 с двойной точностью:
Это потому, что показатель 0.49999999999999994 имеет меньший показатель, чем 0.5, поэтому при их добавлении его мантисса сдвигается, и ULP становится больше.
Решение
Начиная с Java 7, OpenJDK (например) реализует это таким образом:4
publicstaticlonground(double a) { if (a != 0x1.fffffffffffffp-2) // greatest double value less than 0.5 return (long)floor(a + 0.5d); else return0; }
1. http://docs.oracle.com/javase/6/docs/api/java/lang/Math.html#round%28double%29 2. https://bugs.java.com/bugdatabase/view_bug?bug_id=6430675 (спасибо @SimonNickerson за то, что нашел это) 3. http://docs.oracle.com/javase/7/docs/api/java/lang/Math.html#round%28double%29 4. http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/java/lang/Math.java#Math.round%28double%29
publicstaticlonground(double a) { return (long)Math.floor(a + 0.5d); }
Исходный код в JDK 7:
publicstaticlonground(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 { return0; } }
Когда значение равно 0.49999999999999994d, в JDK 6 оно вызовет floor и, следовательно, вернет 1, но в JDK 7 if условием является проверка, является ли число наибольшим двойным значением меньше 0.5 или нет. Поскольку в этом случае число не является наибольшим двойным значением меньше 0.5, поэтому else блок возвращает 0.
Вы можете попробовать 0.49999999999999999 d, который вернет 1, но не 0, потому что это наибольшее двойное значение меньше 0.5.
Ответ 4
У меня то же самое в 32-разрядной версии JDK 1.6, но в 64-разрядной версии Java 7 я получаю 0 для 0.49999999999999994, округление которого равно 0, а последняя строка не печатается. Похоже, это проблема виртуальной машины, однако, используя значения с плавающей запятой, следует ожидать, что результаты будут немного отличаться в разных средах (CPU, 32- или 64-разрядный режим).
И при использовании round или инвертировании матриц и т.д. Эти биты могут иметь огромное значение.
вывод 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