Вопрос-ответ

Why does Math.round(0.49999999999999994) return 1?

Почему 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.

Переведено автоматически
Ответ 1

Краткие сведения

В 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 становится больше.

Решение

Начиная с 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. 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
Ответ 2

Похоже, это известная ошибка (ошибка Java 6430675: Math.round имеет неожиданное поведение для 0x1.fffffffffffffffp-2), которая была исправлена в Java 7.

Ответ 3

Исходный код в 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 условием является проверка, является ли число наибольшим двойным значением меньше 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
2023-05-03 21:18 java