web-dev-qa-db-fra.com

Qu'est-ce qui ne va pas avec == pour comparer les flottants en Java?

Selon cette page Java.Sun== est l'opérateur de comparaison d'égalité pour les nombres à virgule flottante en Java.

Cependant, quand je tape ce code:

if(sectionID == currentSectionID)

dans mon éditeur et lancer une analyse statique, je reçois: "Java0078 Valeurs en virgule flottante comparées à =="

Quel est le problème avec l'utilisation de == pour comparer des valeurs à virgule flottante? Quelle est la bonne façon de le faire?

167
user128807

la bonne façon de tester les flottants pour "l'égalité" est la suivante:

if(Math.abs(sectionID - currentSectionID) < epsilon)

où epsilon est un nombre très petit, tel que 0,00000001, en fonction de la précision souhaitée.

205
Victor

Les valeurs en virgule flottante peuvent être légèrement décalées, elles peuvent donc ne pas être signalées comme étant exactement égales. Par exemple, si vous définissez un nombre float sur "6.1", puis que vous le réimprimez, vous obtiendrez peut-être une valeur de type "6.099999904632568359375". Ceci est fondamental pour le fonctionnement des flotteurs; par conséquent, vous ne voulez pas les comparer en utilisant une égalité, mais plutôt une comparaison dans une plage, c'est-à-dire si le diff du flottant au nombre auquel vous voulez le comparer est inférieur à une certaine valeur absolue.

Cet article sur le registre donne un bon aperçu des raisons pour lesquelles c'est le cas; lecture utile et intéressante.

53
Paul Sonier

Juste pour donner la raison derrière ce que tout le monde dit.

La représentation binaire d'un float est assez ennuyeuse.

En binaire, la plupart des programmeurs connaissent la corrélation entre 1b = 1d, 10b = 2d, 100b = 4d, 1000b = 8d

Eh bien cela fonctionne dans l'autre sens aussi.

.1b = .5d, .01b = .25d, .001b = .125, ...

Le problème est qu’il n’existe aucun moyen exact de représenter la plupart des nombres décimaux tels que .1, .2, .3, etc. Tout ce que vous pouvez faire est approximatif en binaire. Le système arrondit un peu le fudge lorsque les nombres sont imprimés pour afficher .1 au lieu de .10000000000001 ou .999999999999 (qui sont probablement aussi proches de la représentation stockée que celle de .1)

Edit from comment: La raison pour laquelle cela pose un problème réside dans nos attentes. Nous nous attendons totalement à ce que 2/3 soit falsifié à un moment donné lorsque nous le convertirons en décimal, soit 0,7, 0,67 ou 0,666667 .. Mais nous ne prévoyons pas automatiquement que .1 sera arrondi de la même manière que 2/3. - et c'est exactement ce qui se passe.

À propos, si vous êtes curieux, le nombre qu’il stocke en interne est une pure représentation binaire utilisant une "notation scientifique" binaire. Donc, si vous lui dites de stocker le nombre décimal 10.75d, il stockera 1010b pour le nombre 10 et .11b pour le nombre décimal. Donc, il stockerait 0,101011 puis il économisera quelques bits à la fin pour dire: déplacez le point décimal de quatre places vers la droite.

(Bien que techniquement ce ne soit plus un point décimal, c'est maintenant un point binaire, mais cette terminologie n'aurait pas rendu les choses plus compréhensibles pour la plupart des gens qui trouveraient cette réponse utile.)

22
Bill K

Qu'est-ce qui ne va pas avec == pour comparer des valeurs en virgule flottante?

Parce que ce n'est pas vrai que 0.1 + 0.2 == 0.3

19
AakashM

Je pense qu'il y a beaucoup de confusion autour des flotteurs (et des doublons), il est bon de clarifier les choses.

  1. Il n'y a rien de fondamentalement faux dans l'utilisation de floats en tant qu'ID dans une machine virtuelle Java conforme à la norme [*]. Si vous définissez simplement l'ID float sur x, ne faites rien avec cet identifiant (c'est-à-dire sans arithmétique) et testez plus tard pour y == x, tout ira bien. De plus, il n'y a rien de mal à les utiliser comme clés dans un HashMap. Ce que vous ne pouvez pas faire, c'est supposer des égalités telles que x == (x - y) + y, etc. Ceci étant dit, les gens utilisent généralement des types entiers comme identifiants, et vous pouvez constater que la plupart des gens ici sont rebutés par ce code; mieux adhérer aux conventions. Notez qu'il existe autant de valeurs double différentes qu'il y a de longues values, de sorte que vous ne gagnez rien en utilisant double. De plus, générer des "prochains identifiants disponibles" peut être délicat avec des doublons et nécessite quelques connaissances de l'arithmétique en virgule flottante. Ne vaut pas la peine.

  2. En revanche, s’appuyer sur l’égalité numérique des résultats de deux calculs mathématiquement équivalents est risqué. Cela est dû aux erreurs d'arrondi et à la perte de précision lors de la conversion de la représentation décimale en représentation binaire. Cela a été discuté à mort sur SO.

[*] Quand j'ai parlé de "JVM conforme aux normes", je voulais exclure certaines implémentations de JVM endommagées au cerveau. Voir this .

11
quant_dev

Ce problème n’est pas spécifique à Java. L'utilisation de == pour comparer deux flottants/doubles/n'importe quel numéro de type décimal peut potentiellement poser des problèmes en raison de la façon dont ils sont stockés. Un float à simple précision (conforme à la norme IEEE 754) comporte 32 bits, répartis comme suit:

1 bit - Signe (0 = positif, 1 = négatif)
8 bits - Exponent (une représentation spéciale (biais 127) du x dans 2 ^ x)
23 bits - Mantisa. Le numéro actuel qui est stocké.

La mantisa est ce qui cause le problème. C'est un peu comme la notation scientifique, seul le nombre en base 2 (binaire) ressemble à 1.110011 x 2 ^ 5 ou quelque chose de similaire. Mais en binaire, le premier 1 est toujours un 1 (sauf pour la représentation de 0)

Par conséquent, pour économiser un peu d’espace mémoire (jeu de mots prévu), IEEE a décidé que le 1 devait être supposé. Par exemple, une mantisa de 1011 correspond en réalité à 1.1011.

Cela peut poser des problèmes de comparaison, surtout avec 0, car 0 ne peut pas être représenté exactement dans un float. C'est la raison principale pour laquelle == est découragée, en plus des problèmes mathématiques en virgule flottante décrits par d'autres réponses.

Java a un problème unique en ce sens que le langage est universel sur de nombreuses plates-formes différentes, chacune d'entre elles pouvant avoir son propre format flottant. Cela rend d'autant plus important d'éviter ==.

La manière appropriée de comparer deux flottants (vous ne vous occupez pas de la langue spécifique) est la suivante:

if(ABS(float1 - float2) < ACCEPTABLE_ERROR)
    //they are approximately equal

où ACCEPTABLE_ERROR est #defined ou une autre constante égale à 0.000000001 ou la précision requise, comme déjà mentionné par Victor.

Certaines langues ont cette fonctionnalité ou cette constante intégrée, mais généralement c'est une bonne habitude.

8
CodeFusionMobile

Les valeurs du point de rejet ne sont pas fiables, en raison d'une erreur d'arrondi.

En tant que telles, elles ne devraient probablement pas être utilisées comme valeurs de clé, telles que sectionID. Utilisez des nombres entiers à la place, ou long si int ne contient pas suffisamment de valeurs possibles.

8
Eric Wilson

En plus des réponses précédentes, vous devez savoir qu’il existe des comportements étranges associés à -0.0f et +0.0f (ils sont == mais pas equals) et Float.NaN. (c'est equals mais pas ==) (j'espère que j'ai raison - argh, ne le fais pas!).

Edit: vérifions!

import static Java.lang.Float.NaN;
public class Fl {
    public static void main(String[] args) {
        System.err.println(          -0.0f   ==              0.0f);   // true
        System.err.println(new Float(-0.0f).equals(new Float(0.0f))); // false
        System.err.println(            NaN   ==               NaN);   // false
        System.err.println(new Float(  NaN).equals(new Float( NaN))); // true
    }
} 

Bienvenue dans IEEE/754.

7

À ce jour, le moyen rapide et facile de le faire est:

if (Float.compare(sectionID, currentSectionID) == 0) {...}

Toutefois, les docs ne spécifient pas clairement la valeur de la différence de marge (un epsilon de la réponse de @Victor) qui est toujours présent dans les calculs sur les flottants, mais cela devrait être quelque chose de raisonnable, car cela fait partie de la bibliothèque de langues standard.

Pourtant, si une précision supérieure ou personnalisée est nécessaire, alors

float epsilon = Float.MIN_NORMAL;  
if(Math.abs(sectionID - currentSectionID) < epsilon){...}

est une autre option de solution.

7
alisa

Voici une très longue discussion (mais utile, espérons-le) sur cette question et de nombreux autres problèmes ponctuels que vous pourriez rencontrer: Ce que tout informaticien devrait savoir sur l'arithmétique en virgule flottante

6
Adam Goode

Vous pouvez utiliser Float.floatToIntBits ().

Float.floatToIntBits(sectionID) == Float.floatToIntBits(currentSectionID)
4
aamadmi

Ce qui suit utilise automatiquement la meilleure précision:

/**
 * Compare to floats for (almost) equality. Will check whether they are
 * at most 5 ULP apart.
 */
public static boolean isFloatingEqual(float v1, float v2) {
    if (v1 == v2)
        return true;
    float absoluteDifference = Math.abs(v1 - v2);
    float maxUlp = Math.max(Math.ulp(v1), Math.ulp(v2));
    return absoluteDifference < 5 * maxUlp;
}

Bien entendu, vous pouvez choisir plus ou moins de 5 ULP ("unité à la dernière place").

Si vous êtes dans la bibliothèque Apache Commons, la classe Precision a compareTo() et equals() avec epsilon et ULP.

4
Michael Piefel

Tout d’abord, est-ce qu’ils flottent ou flottent? Si l'un d'entre eux est un flottant, vous devez utiliser la méthode equals (). Aussi, probablement préférable d'utiliser la méthode statique Float.compare.

4
omerkudat

vous voudrez peut-être que ce soit ==, mais 123.4444444444443! = 123.4444444444442

3
KM.

Si vous devez * utiliser * floats, le mot clé strictfp peut être utile.

http://en.wikipedia.org/wiki/strictfp

3
sdcvvc

Deux calculs différents qui produisent des nombres réels égaux ne produisent pas nécessairement des nombres égaux en virgule flottante. Les gens qui utilisent == pour comparer les résultats des calculs finissent généralement par être surpris, alors l'avertissement aide à signaler ce qui pourrait autrement être un bogue subtil et difficile à reproduire.

2
VoiceOfUnreason

Avez-vous affaire à un code externalisé qui utiliserait des flottants pour les éléments nommés sectionID et currentSectionID? Juste curieux.

@ Bill K: "La représentation binaire d'un float est un peu gênante." Comment? Comment le ferais-tu mieux? Certains nombres ne peuvent être représentés dans aucune base correctement, car ils ne finissent jamais. Pi est un bon exemple. Vous pouvez seulement approximer. Si vous avez une meilleure solution, contactez Intel.

2
xcramps

Comme mentionné dans d'autres réponses, les doubles peuvent avoir de petites déviations. Et vous pourriez écrire votre propre méthode pour les comparer en utilisant une déviation "acceptable". Toutefois ...

Il existe une classe Apache pour comparer les doublons: org.Apache.commons.math3.util.Precision

Il contient des constantes intéressantes: SAFE_MIN et EPSILON, qui sont les déviations maximales possibles d'opérations arithmétiques simples.

Il fournit également les méthodes nécessaires pour comparer, doubles égaux ou arrondis. (en ulps ou en déviation absolue)

1
bvdb

En une ligne, je peux dire que vous devriez utiliser:

Float.floatToIntBits(sectionID) == Float.floatToIntBits(currentSectionID)

Pour vous aider à en apprendre davantage sur l’utilisation correcte des opérateurs liés, j’explique quelques cas ici: En général, il existe trois méthodes pour tester les chaînes en Java. Vous pouvez utiliser ==, .equals () ou Objects.equals ().

Comment sont-ils différents? == teste la qualité de référence dans les chaînes, ce qui permet de déterminer si les deux objets sont identiques. D'autre part, .equals () teste si les deux chaînes sont logiquement de valeur égale. Enfin, Objects.equals () recherche les valeurs NULL dans les deux chaînes, puis détermine s'il convient d'appeler .equals ().

Opérateur idéal à utiliser

Cela a fait l’objet de nombreux débats, car chacun des trois opérateurs a ses forces et ses faiblesses. Par exemple, == est souvent une option privilégiée lors de la comparaison d'une référence d'objet, mais dans certains cas, il peut sembler que la comparaison des valeurs de chaîne soit également possible.

Cependant, ce que vous obtenez est une valeur de chute, car Java crée l'illusion que vous comparez des valeurs, mais pas vraiment. Considérons les deux cas ci-dessous:

Cas 1:

String a="Test";
String b="Test";
if(a==b) ===> true

Cas 2:

String nullString1 = null;
String nullString2 = null;
//evaluates to true
nullString1 == nullString2;
//throws an exception
nullString1.equals(nullString2);

Il est donc préférable d’utiliser chaque opérateur lors du test de l’attribut spécifique pour lequel il est conçu. Mais dans presque tous les cas, Objects.equals () est un opérateur plus universel et permet donc aux développeurs Web de choisir cette option.

Ici vous pouvez obtenir plus de détails: http://fluentthemes.com/use-compare-strings-Java/

1
Fluent-Themes

La bonne façon serait

Java.lang.Float.compare(float1, float2)
0
Eric