redondear - round double value java




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

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.

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.



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







rounding