562 votes

Pourquoi Math.round (0.49999999999999994) renvoie 1

Dans le programme suivant, vous pouvez voir que pour chaque valeur, un peu moins de 0,5 est arrondi vers le bas, sauf pour 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.

573voto

Oli Charlesworth Points 148744

Résumé

Dans la version 6 de Java (et probablement plus tôt), round(x) est mis en œuvre en tant que floor(x+0.5).1 C'est une spécification de bug, c'est précisément pour cette un cas pathologique.2 Java 7 n'est plus de mandats de ce cassé la mise en œuvre.3

Le problème

0.5+0.49999999999999994 est exactement de 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 plus petit exposant de 0,5, donc quand ils sont ajoutés, sa mantisse est décalé, et l'ULP est plus grand.

La solution

Depuis Java 7, OpenJDK (par exemple) met en œuvre 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 de @SimonNickerson pour trouver ce)

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

232voto

Simon Nickerson Points 17147

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.

83voto

Chandra Sekhar Points 6512

le code source du JDK 6

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

le code source du 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.49999999999999994 d, dans le JDK 6, il va appeler sol et donc retourne 1, mais dans le JDK 7, si la condition est de vérifier si le nombre est plus du double de la valeur d'au moins 0,5 ou pas. Comme dans ce cas, le nombre n'est pas la plus grande du double de la valeur est inférieure à 0,5, de sorte que le reste du bloc renvoie 0.

Vous pouvez essayer de 0.49999999999999999 d, qui sera de retour 1, mais pas 0, parce que c'est la plus grande valeur double de moins de 0,5.

26voto

Lukasz Points 9471

J’ai la même chose sur jdk 1,6 32 bits, mais sur 64 bits de java 7, j’ai passez 0 pour 0.49999999999999994 arrondi est 0 et la dernière ligne n’est pas imprimée. Il semble être question VM, cependant, à l’aide de points flottants, vous devriez vous attendre les résultats diffèrent un peu sur les différents environnements (CPU, mode 33 - ou 64-bit)

Et, lorsque vous utilisez `` ou inversion de matrices ou etc, cette bits peut faire une énorme différence.

x64 de sortie :

11voto

shiv.mymail Points 338

La réponse ci-après est un extrait de l'oracle rapport de bug à http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6430675. Visitez le lien pour l'explication complète.

Les méthodes {Math, StrictMath.tour opérationnelle est définie comme

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

pour le double arguments. Bien que cette définition généralement fonctionne comme prévu, il donne le résultat surprenant de 1, au lieu de 0, pour 0 x 1.fffffffffffffp-2 (0.49999999999999994).

La valeur 0.49999999999999994 est la plus grande valeur à virgule flottante d'au moins 0,5. Comme hexadécimal à virgule flottante littérale sa valeur est 0 x 1.fffffffffffffp-2, 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. C'est à mi-chemin entre les deux nombres à virgule flottante adjacents (1 - 2^53) et 1. Dans la norme IEEE 754 arithmétique tour la plus proche, même mode d'arrondi utilisé par Java, en virgule flottante résultats est inexact, la plus proche des deux représentable des valeurs à virgule flottante qui support le résultat exact doit être retourné; si les deux valeurs sont proches de la même façon, celui qui, à son dernier bit zéro est renvoyée. Dans ce cas, la bonne valeur de retour de l'ajouter, c'est 1, pas la plus grande valeur inférieure à 1.

Bien que la méthode d'exploitation est tel que défini, le comportement sur cette entrée est très surprenant; la spécification coudl être ameneded à quelque chose de plus comme "la Tour la plus proche de long, arrondissement les liens," qui permettrait de comportement sur cette entrée pour être changé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X