Question Pourquoi Math.round (0.49999999999999994) renvoie 1?


Dans le programme suivant, vous pouvez voir que chaque valeur légèrement inférieure à .5 est arrondi à l'exception de 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);
}

imprime

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

J'utilise Java 6 mise à jour 31.


530
2018-03-28 07:30


origine


Réponses:


Résumé

En Java 6 (et probablement plus tôt), round(x) est mis en œuvre comme floor(x+0.5).1   C'est un bug de spécification, précisément pour ce cas pathologique.2  Java 7 n'impose plus cette implémentation interrompue.3

Le problème

0.5 + 0.49999999999999994 est exactement 1 en double précision:

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
}

C'est parce que 0.49999999999999994 a un exposant plus petit que 0.5, donc quand ils sont ajoutés, sa mantisse est décalée, et l'ULP devient plus grande.

La solution

Depuis Java 7, OpenJDK (par exemple) l'implémente ainsi: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édits à @SimonNickerson pour trouver ceci)

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


546
2018-03-28 07:38



Cela semble être un bug connu (Bogue Java 6430675: Math.round a un comportement surprenant pour 0x1.fffffffffffffp-2) qui a été corrigé dans Java 7.


228
2018-03-28 07:53



Code source dans JDK 6:

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

Code source dans 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;
    }
}

Lorsque la valeur est 0.49999999999999994d, dans JDK 6, il appellera sol et renvoie donc 1, mais dans JDK 7, le if condition vérifie si le nombre est la plus grande valeur double inférieure à 0,5 ou non. Comme dans ce cas, le nombre n'est pas la plus grande valeur double inférieure à 0,5, de sorte que le else bloc renvoie 0.

Vous pouvez essayer 0.49999999999999999d, ce qui retournera 1, mais pas 0, car c'est la plus grande valeur double inférieure à 0.5.


79
2018-03-28 08:29



J'ai le même sur JDK 1.6 32 bits, mais sur Java 7 64 bits, j'ai 0 pour 0.49999999999999994 qui arrondi est 0 et la dernière ligne n'est pas imprimée. Il semble que ce soit un problème de machine virtuelle, cependant, en utilisant des points flottants, vous devriez vous attendre à ce que les résultats diffèrent un peu sur différents environnements (CPU, mode 32 ou 64 bits).

Et, en utilisant round ou inverser des matrices, etc., ces morceaux peut faire une énorme différence.

sortie 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

26
2018-03-28 07:42



La réponse ci-après est un extrait d'un Oracle  rapport de bug 6430675 à. Visitez le rapport pour l'explication complète.

Les méthodes {Math, StrictMath.round sont définies opérationnellement comme

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

pour les arguments doubles. Alors que cette définition fonctionne normalement comme prévu, elle donne le résultat surprenant de 1, plutôt que 0, pour 0x1.fffffffffffffp-2 (0.49999999999999994).

La valeur 0,49999999999999994 est la plus grande valeur à virgule flottante inférieure à 0,5. En tant que littéral à virgule flottante hexadécimal, sa valeur est 0x1.fffffffffffffp-2, ce qui est égal à (2 - 2 ^ 52) * 2 ^ -2. == (0,5 - 2 ^ 54). Par conséquent, la valeur exacte de la somme

(0.5 - 2^54) + 0.5

est 1 - 2 ^ 54. Ceci est à mi-chemin entre les deux nombres flottants adjacents (1 - 2 ^ 53) et 1. Dans le cycle arithmétique IEEE 754 au mode arrondi le plus proche utilisé par Java, lorsqu'un résultat à virgule flottante est inexact, le plus proche des deux les valeurs flottantes représentables qui encadrent le résultat exact doivent être retournées; si les deux valeurs sont également proches, celle dont le dernier bit zéro est retourné. Dans ce cas, la valeur de retour correcte de l'ajout est 1, pas la plus grande valeur inférieure à 1.

Alors que la méthode fonctionne comme définie, le comportement sur cette entrée est très surprenant; la spécification pourrait être modifiée pour ressembler à quelque chose comme «Arrondir au plus proche, arrondir les liens vers le haut», ce qui permettrait de modifier le comportement de cette entrée.


11
2018-06-05 11:19