round redondear decimals decimales java floating-point double rounding

redondear - round double java



¿Por qué Math.round(0.4999999999999999994) devuelve 1? (5)

En el siguiente programa puede ver que cada valor ligeramente inferior a .5 se redondea hacia abajo, excepto para 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 Java 6 update 31.


Código fuente en JDK 6:

public static long round(double a) { return (long)Math.floor(a + 0.5d); }

Código fuente en 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; } }

Cuando el valor es 0.49999999999999994d, en JDK 6, llamará piso y por lo tanto devuelve 1, pero en JDK 7, la condición if está verificando si el número es el valor de doble mayor menor que 0.5 o no. Como en este caso, el número no es el mayor valor doble menor que 0.5, por lo que el bloque else devuelve 0.

Puede probar 0.49999999999999999d, que devolverá 1, pero no 0, porque este es el mayor valor doble menor que 0.5.



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 operativamente como

(long)Math.floor(a + 0.5d)

Para los argumentos dobles. Si bien esta definición generalmente funciona como se esperaba, da el resultado sorprendente de 1, en lugar de 0, para 0x1.ffffffffffffff-2 (0.49999999999999994).

El valor 0.49999999999999994 es el mayor valor de punto flotante menor que 0.5. Como literal de punto flotante hexadecimal, su valor es 0x1.ffffffffffffff-2, que es igual a (2 - 2 ^ 52) * 2 ^ -2. == (0.5 - 2 ^ 54). Por lo tanto, el valor exacto de la suma

(0.5 - 2^54) + 0.5

es 1 - 2 ^ 54. Esto está a mitad de camino entre los dos números de punto flotante adyacentes (1 - 2 ^ 53) y 1. En la ronda aritmética IEEE 754 hasta el modo de redondeo más cercano usado por Java, cuando los resultados de punto flotante son inexactos, el más cercano de los dos valores de punto flotante representables que encierran el resultado exacto deben devolverse; si ambos valores están igualmente cerca, el que devuelve su último bit cero. En este caso, el valor de retorno correcto de la suma es 1, no el valor mayor menor que 1.

Si bien el método funciona como se define, el comportamiento en esta entrada es muy sorprendente; la especificación podría modificarse a algo más parecido a "Redondear al largo más cercano, redondeando los amarres", lo que permitiría cambiar el comportamiento de esta entrada.


Tengo lo mismo en JDK 1.6 de 32 bits, pero en Java 7 de 64 bits tengo 0 para 0.4999999999999999994 que se redondea a 0 y la última línea no se imprime. Parece ser un problema de VM, sin embargo, al utilizar puntos flotantes, debe esperar que los resultados difieran un poco en varios entornos (CPU, modo de 32 o 64 bits).

Y, cuando se usan matrices round o invertidas, etc., estos bits pueden hacer una gran diferencia.

salida 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


Resumen

En Java 6 (y probablemente antes), round(x) se implementa como floor(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

El problema

0.5 + 0.49999999999999994 es exactamente 1 en doble precisión:

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 }

Esto se debe a que 0.4999999999999999994 tiene un exponente más pequeño que 0.5, por lo que cuando se agregan, su mantisa cambia y la ULP se hace más grande.

La solución

Desde Java 7, OpenJDK (por ejemplo) lo implementa así: 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 (créditos para @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