web-dev-qa-db-fra.com

Pourquoi ne pouvons-nous pas utiliser '==' pour comparer deux nombres flottants ou doubles

Je lis Efficace Java par Joshua Bloch et dans Point 8: Obéissez au contrat général en remplaçant égal), cette déclaration est écrite

pour les champs flottants, utilisez la méthode Float.compare; et pour les champs doubles, utilisez Double.compare. Le traitement spécial des champs float et double est rendu nécessaire par l'existence de Float.NaN, -0.0f et des constantes doubles analogues;

Quelqu'un peut-il m'expliquer par exemple pourquoi nous ne pouvons pas utiliser == pour flottant ou double comparaison

26
Anand

Depuis apidoc, Float.compare :

Compare les deux valeurs flottantes spécifiées. Le signe de la valeur entière retournée est le même que celui de l'entier qui serait retourné par l'appel:

nouveau flotteur (f1) .compareTo (nouveau flotteur (f2))

Float.compareTo :

Compare numériquement deux objets Float. Il existe deux façons dont les comparaisons effectuées par cette méthode diffèrent de celles effectuées par les opérateurs de comparaison numérique du langage Java langage (<, <=, ==,> =>) lorsqu'ils sont appliqués à des valeurs flottantes primitives :

  • Float.NaN est considéré par cette méthode comme étant égal à lui-même et supérieur à toutes les autres valeurs flottantes (y compris Float.POSITIVE_INFINITY).
  • 0,0f est considéré par cette méthode comme supérieur à -0,0f.

Cela garantit que l'ordre naturel des objets Flottant imposé par cette méthode est cohérent avec des égaux.

Considérez le code suivant:

    System.out.println(-0.0f == 0.0f); //true
    System.out.println(Float.compare(-0.0f, 0.0f) == 0 ? true : false); //false      
    System.out.println(Float.NaN == Float.NaN);//false
    System.out.println(Float.compare(Float.NaN, Float.NaN) == 0 ? true : false); //true
    System.out.println(-0.0d == 0.0d); //true
    System.out.println(Double.compare(-0.0d, 0.0d) == 0 ? true : false);//false     
    System.out.println(Double.NaN == Double.NaN);//false
    System.out.println(Double.compare(Double.NaN, Double.NaN) == 0 ? true : false);//true        

La sortie n'est pas correcte, car quelque chose qui n'est pas un nombre, n'est tout simplement pas un nombre et doit être traité comme égal du point de vue de la comparaison des nombres. Il est également clair que 0=-0.

Voyons voir ce que Float.compare fait:

public static int compare(float f1, float f2) {
   if (f1 < f2)
        return -1;           // Neither val is NaN, thisVal is smaller
    if (f1 > f2)
        return 1;            // Neither val is NaN, thisVal is larger

    int thisBits = Float.floatToIntBits(f1);
    int anotherBits = Float.floatToIntBits(f2);

    return (thisBits == anotherBits ?  0 : // Values are equal
            (thisBits < anotherBits ? -1 : // (-0.0, 0.0) or (!NaN, NaN)
             1));                          // (0.0, -0.0) or (NaN, !NaN)
}

Float.floatToIntBits :

Renvoie une représentation de la valeur à virgule flottante spécifiée conformément à la disposition de bits à virgule flottante IEEE 754 Bit 31 (le bit qui est sélectionné par le masque 0x80000000) représente le signe du nombre à virgule flottante. Les bits 30 à 23 (les bits sélectionnés par le masque 0x7f800000) représentent l'exposant. Les bits 22 les bits sélectionnés par le masque 0x007fffff) représentent la signification (parfois appelée la mantisse) du nombre à virgule flottante.

Si l'argument est l'infini positif, le résultat est 0x7f800000.

Si l'argument est l'infini négatif, le résultat est 0xff800000.

Si l'argument est NaN, le résultat est 0x7fc00000.

Dans tous les cas, le résultat est un entier qui, lorsqu'il est donné à la méthode intBitsToFloat (int), produira une valeur à virgule flottante identique à l'argument de floatToIntBits ( sauf que toutes les valeurs NaN sont réduit à une seule valeur NaN "canonique" ).

De --- (JLS 15.20.1. Opérateurs de comparaison numérique <, <=,> et> =

Le résultat d'une comparaison à virgule flottante, tel que déterminé par la spécification de la norme IEEE 754, est:

  • Si l'un des opérandes est NaN, le résultat est faux.

  • Toutes les valeurs autres que NaN sont ordonnées, avec une infinité négative inférieure à toutes les valeurs finies et une infinité positive supérieure à toutes les valeurs finies.

  • Le zéro positif et le zéro négatif sont considérés comme égaux. Par exemple, -0,0 <0,0 est faux, mais -0,0 <= 0,0 est vrai.

  • Notez, cependant, que les méthodes Math.min et Math.max traitent le zéro négatif comme étant strictement inférieur au zéro positif.

Pour les comparaisons strictes où les opérandes sont zéro positif et zéro négatif, le résultat sera faux.

De JLS 15.21.1. Opérateurs d'égalité numérique == et! = :

Le résultat d'une comparaison à virgule flottante, tel que déterminé par la spécification de la norme IEEE 754, est:

Les tests d'égalité en virgule flottante sont effectués conformément aux règles de la norme IEEE 754:

  • Si l'un des opérandes est NaN, alors le résultat de == est faux mais le résultat de! = Est vrai. En effet, le test x! = X est vrai si et seulement si la valeur de x est NaN. Les méthodes Float.isNaN et Double.isNaN peuvent également être utilisées pour tester si une valeur est NaN.

  • Le zéro positif et le zéro négatif sont considérés comme égaux. Par exemple, -0,0 == 0,0 est vrai.

  • Sinon, deux valeurs distinctes en virgule flottante sont considérées comme inégales par les opérateurs d'égalité. En particulier, il existe une valeur représentant l'infini positif et une valeur représentant l'infini négatif; chacun ne se compare qu'à lui-même et chacun se compare à toutes les autres valeurs.

Pour les comparaisons d'égalité où les deux opérandes sont NaN le résultat sera faux.

Depuis commande totale (=, <, >, <=, >=) est utilisé par de nombreux algorithmes importants (voir toutes les classes qui implémentent l'interface Comparable ) il est préférable d'utiliser la méthode compare car elle donnera un comportement plus cohérent.

La conséquence du classement total dans le contexte de la norme IEEE-754 est la différence entre le zéro positif et le zéro négatif.

Par exemple, si vous utilisez l'opérateur d'égalité au lieu de la méthode de comparaison, et que vous avez une collection de valeurs et que votre logique de code prend des décisions en fonction de l'ordre des éléments, et que vous commencez à obtenir un surplus de valeurs NaN, ils vont tous être traités comme des valeurs différentes plutôt que comme les mêmes valeurs.

Cela peut probablement produire une erreur dans le comportement du programme proportionnelle à la quantité/taux de valeurs de NaN. Et si vous avez beaucoup de zéros positifs et négatifs, ce n'est qu'une paire pour affecter votre logique avec erreur.

Flottant tilise IEEE-754 au format 32 bits et Double tilise IEEE-754 au format 64 bits.

18
linski

float (et double) ont des séquences de bits spéciales qui sont réservées à des significations spéciales qui ne sont pas des "nombres":

  • Infini négatif, représentation interne 0xff800000
  • Infini positif, représentation interne 0x7f800000
  • Pas un nombre, représentation interne 0x7fc00000

Chacune de ces valeurs renvoie 0 (Ce qui signifie qu'elles sont "identiques") par rapport à elle-même à l'aide de Float.compare(), mais les comparaisons suivantes utilisant == Diffèrent de cela pour Float.NaN:

Float.NEGATIVE_INFINITY == Float.NEGATIVE_INFINITY // true
Float.POSITIVE_INFINITY == Float.POSITIVE_INFINITY // true
Float.NaN == Float.NaN // false

Ainsi, lors de la comparaison des valeurs de float, pour être cohérent pour toutes les valeurs, y compris la valeur spéciale Float.NaN, Float.compare() est la meilleure option.

La même chose s'applique à double.

5
Bohemian

Il y a deux raisons de comparer des objets à virgule flottante:

  • Je fais des maths, donc je veux comparer leurs valeurs numériques. Numériquement, –0 est égal à +0, et un NaN n'est égal à rien, pas même lui-même, car "égal" est une propriété que seuls les nombres ont, et NaN n'est pas un nombre.
  • Je travaille avec des objets dans un ordinateur, j'ai donc besoin de distinguer différents objets et de les placer dans l'ordre. Cela est nécessaire pour trier des objets dans une arborescence ou un autre conteneur, par exemple.

L'opérateur == Fournit des comparaisons mathématiques. Il renvoie faux pour NaN == NaN Et vrai pour -0.f == +0.f

Les routines compare et compareTo fournissent des comparaisons d'objets. En comparant un NaN à lui-même, ils indiquent qu'il est le même (en retournant zéro). En comparant -0.f À +0.f, Ils indiquent qu'ils sont différents (en retournant non nul).

2
Eric Postpischil