En el siguiente programa, puede ver que cada valor es ligeramente inferior al .5
redondeado hacia abajo, excepto 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);
}
huellas dactilares
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
Estoy usando la actualización de Java 6 31.
java
floating-point
double
rounding
Peter Lawrey
fuente
fuente
0.5
al número y luego usarfloor
; Java 7 ya no lo documenta de esa manera (presumiblemente / con suerte porque lo arreglaron).Respuestas:
Resumen
En Java 6 (y presumiblemente antes),
round(x)
se implementa comofloor(x+0.5)
. 1 Este es un error de especificación, precisamente para este caso patológico. 2 Java 7 ya no exige esta implementación rota. 3 3El problema
0.5 + 0.49999999999999994 es exactamente 1 en doble precisión:
Esto se debe a que 0.49999999999999994 tiene un exponente menor que 0.5, por lo que cuando se agregan, su mantisa se desplaza y el ULP se hace más grande.
La solución
Desde Java 7, OpenJDK (por ejemplo) lo implementa así: 4
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 (créditos a @SimonNickerson por encontrar esto)
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
fuente
round
en el Javadoc paraMath.round
o en la descripción general de laMath
clase.Esto parece ser un error conocido (error de Java 6430675: Math.round tiene un comportamiento sorprendente para 0x1.fffffffffffffp-2 ) que se ha solucionado en Java 7.
fuente
Código fuente en JDK 6:
Código fuente en JDK 7:
Cuando el valor es 0.49999999999999994d, en JDK 6, llamará a piso y, por lo tanto, devuelve 1, pero en JDK 7, la
if
condición es verificar si el número es el mayor valor doble menor que 0.5 o no. Como en este caso el número no es el mayor valor doble menor que 0.5, entonces elelse
bloque devuelve 0.Puede probar 0.49999999999999999d, que devolverá 1, pero no 0, porque este es el valor doble más grande menor que 0.5.
fuente
floor
método lo redondea correctamente.Tengo lo mismo en JDK 1.6 de 32 bits, pero en Java 7 de 64 bits tengo 0 para 0.49999999999999994 que redondeado es 0 y la última línea no se imprime. Parece ser un problema de VM, sin embargo, al usar puntos flotantes, debe esperar que los resultados difieran un poco en varios entornos (CPU, modo de 32 o 64 bits).
Y, al usar
round
o invertir matrices, etc., estos bits pueden hacer una gran diferencia.salida x64:
fuente
La respuesta a continuación es un extracto de un informe de error de Oracle 6430675 en. Visite el informe para la explicación completa.
Los métodos {Math, StrictMath.round se definen operacionalmente como
por dobles argumentos. Si bien esta definición generalmente funciona como se esperaba, ofrece el sorprendente resultado de 1, en lugar de 0, para 0x1.fffffffffffffp-2 (0.49999999999999994).
El valor 0.49999999999999994 es el mayor valor de coma flotante menor que 0.5. Como literal hexadecimal de coma flotante, su valor es 0x1.fffffffffffffp-2, que es igual a (2 - 2 ^ 52) * 2 ^ -2. == (0.5 - 2 ^ 54). Por lo tanto, el valor exacto de la suma
es 1 - 2 ^ 54. Esto está a medio camino entre los dos números adyacentes de punto flotante (1 - 2 ^ 53) y 1. En el redondeo aritmético IEEE 754 al modo de redondeo par más cercano utilizado por Java, cuando un resultado de punto flotante es inexacto, el más cercano de los dos los valores representables de coma flotante que entre paréntesis deben devolver el resultado exacto; si ambos valores son igualmente cercanos, se devuelve el último bit cero. En este caso, el valor de retorno correcto de la suma es 1, no el mayor valor menor que 1.
Mientras el método funciona como se define, el comportamiento en esta entrada es muy sorprendente; la especificación podría enmendarse a algo más como "Redondear al más largo, el redondeo se vincula", lo que permitiría cambiar el comportamiento de esta entrada.
fuente