Quel est le problème avec l'utilisation de == pour comparer des flottants en Java?

177

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

Cependant, lorsque je tape ce code:

if(sectionID == currentSectionID)

dans mon éditeur et exécuter une analyse statique, j'obtiens: "JAVA0078 Valeurs à virgule flottante comparées à =="

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

user128807
la source
29
Parce que comparer les flottants avec == est problématique, il est déconseillé de les utiliser comme ID; les noms dans votre exemple de code suggèrent que c'est ce que vous faites; les entiers longs (longs) sont préférés, et la norme de facto pour les ID.
Carl Manaster
4
Ouais, était-ce juste un exemple aléatoire ou utilisez-vous réellement des flottants comme identifiants? Y a-t-il une raison?
Par Wiklander
5
"pour les champs float, 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; voir la documentation Float.equals pour plus de détails. " (Joshua Bloch: Effective Java)
lbalazscs

Réponses:

211

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

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

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

Victor
la source
26
Voir le lien dans la réponse acceptée ( cygnus-software.com/papers/comparingfloats/comparingfloats.htm ) pour savoir pourquoi un epsilon fixe n'est pas toujours une bonne idée. Plus précisément, comme les valeurs dans les flottants comparés deviennent grandes (ou petites), l'epsilon n'est plus approprié. (L'utilisation d'epsilon est bien si vous savez que vos valeurs flottantes sont toutes relativement raisonnables, cependant.)
PT
1
@PT Peut-il multiplier epsilon avec un seul chiffre et changer de fonction if(Math.abs(sectionID - currentSectionID) < epsilon*sectionIDpour résoudre ce problème?
enthousiastegeek
3
C'est peut-être même la meilleure réponse à ce jour, mais elle est toujours imparfaite. D'où vient l'epsilon?
Michael Piefel
1
@MichaelPiefel il dit déjà: "selon la précision souhaitée". Les flotteurs, de par leur nature, sont un peu comme des valeurs physiques: vous n'êtes intéressé que par un nombre limité de positions en fonction de l'inexactitude totale, toute différence au-delà de cela est considérée comme sans objet.
ivan_pozdeev
Mais l'OP voulait vraiment seulement tester l'égalité, et comme on sait que cela n'est pas fiable, il doit utiliser une méthode différente. Pourtant, je ne pense pas qu'il sache quelle est sa «précision désirée»; donc si tout ce que vous voulez, c'est un test d'égalité plus fiable, la question demeure: d'où obtenez-vous l'epsilon? J'ai proposé d'utiliser Math.ulp()dans ma réponse à cette question.
Michael Piefel
53

Les valeurs à virgule flottante peuvent être légèrement décalées, de sorte qu'elles peuvent ne pas être rapportées comme exactement égales. Par exemple, en définissant un flottant sur "6.1" et en l'imprimant à nouveau, vous pouvez obtenir une valeur rapportée de quelque chose comme "6.099999904632568359375". Ceci est fondamental pour le fonctionnement des flotteurs; par conséquent, vous ne voulez pas les comparer en utilisant l'égalité, mais plutôt une comparaison dans une plage, c'est-à-dire si la différence entre le flottant et le nombre auquel vous souhaitez le comparer est inférieure à 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.

Paul Sonier
la source
@kevindtimm: donc vous ferez vos tests d'égalité comme ceci alors si (nombre == 6.099999904632568359375) chaque fois que vous souhaitez savoir que le nombre est égal à 6,1 ... Oui vous avez raison ... tout dans l'ordinateur est strictement déterministe, juste que les approximations utilisées pour les flottants sont contre-intuitives lors de la résolution de problèmes mathématiques.
Newtopian
Les valeurs en virgule flottante ne sont imprécises de manière non déterministe que sur un matériel très spécifique .
Stuart P. Bentley
1
@Stuart Je peux me tromper, mais je ne pense pas que le bogue FDIV était non déterministe. Les réponses données par le matériel n'étaient pas conformes aux spécifications, mais elles étaient déterministes, en ce que le même calcul produisait toujours le même résultat incorrect
Gravity
@Gravity Vous pouvez affirmer que tout comportement est déterministe compte tenu d'un ensemble spécifique de mises en garde.
Stuart P. Bentley
Les valeurs à virgule flottante ne sont pas imprécises. Chaque valeur à virgule flottante est exactement ce qu'elle est. Ce qui peut être imprécis est le résultat d'un calcul en virgule flottante . Mais méfiez-vous! Lorsque vous voyez quelque chose comme 0,1 dans un programme, ce n'est pas une valeur à virgule flottante. C'est un littéral à virgule flottante - une chaîne que le compilateur convertit en une valeur à virgule flottante en effectuant un calcul .
Solomon Slow
22

Juste pour donner la raison de ce que tout le monde dit.

La représentation binaire d'un flotteur est plutôt ennuyeuse.

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

Eh bien, cela fonctionne aussi dans l'autre sens.

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

Le problème est qu'il n'y a pas de moyen exact de représenter la plupart des nombres décimaux tels que .1, .2, .3, etc. Tout ce que vous pouvez faire est une approximation en binaire. Le système fait un peu de fudge-arrondi lorsque les nombres s'impriment pour afficher 0,1 au lieu de .10000000000001 ou .999999999999 (qui sont probablement aussi proches de la représentation stockée que .1)

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

Au fait, 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 10 et .11b pour la décimale. Donc, il stockerait .101011 puis il économisait 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 de n'importe quelle utilité.)

Bill K
la source
1
@Matt K - um, pas de point fixe; si vous "sauvegardez quelques bits à la fin pour dire déplacer le point décimal [N] bits vers la droite", c'est une virgule flottante. Le point fixe prend la position du point de base pour être, eh bien, fixe. De plus, en général, comme le décalage du point binamal (?) Peut toujours être amené à vous laisser un '1' dans la position la plus à gauche, vous trouverez des systèmes qui omettent le '1' de début, consacrant l'espace ainsi libéré (1 bit!) pour étendre la plage de l'exposant.
JustJeff
Le problème n'a rien à voir avec la représentation binaire ou décimale. Avec la virgule flottante décimale, vous avez toujours des choses comme (1/3) * 3 == 0.99999999999999999999999999.
dan04
2
@ dan04 oui, parce que 1/3 n'a pas de représentation décimale OU binaire, il a une représentation trinaire et se convertirait correctement de cette façon :). Les nombres que j'ai énumérés (.1, .25, etc.) ont tous des représentations décimales parfaites mais pas de représentation binaire - et les gens sont habitués à ceux qui ont des représentations «exactes». BCD les gérerait parfaitement. Voilà la différence.
Bill K
1
Cela devrait avoir beaucoup plus de votes positifs, car il décrit le VRAI problème derrière le problème.
Levite
19

Quel est le problème avec l'utilisation de == pour comparer des valeurs en virgule flottante?

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

AakashM
la source
7
et quoi Float.compare(0.1f+0.2f, 0.3f) == 0?
Aquarius Power
0,1f + 0,2f == 0,3f mais 0,1d + 0,2d! = 0,3d. Par défaut, 0,1 + 0,2 est un double. 0,3 est également un double.
burnabyRails
12

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

  1. Il n'y a rien de mal en soi à utiliser des flottants comme ID dans la JVM conforme à la norme [*]. Si vous définissez simplement l'ID flottant sur x, ne faites rien avec (c'est-à-dire pas d'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. . Notez qu'il y a autant de doublevaleurs différentes que de valeurs longues values, donc vous ne gagnez rien en utilisant double. De plus, la génération du «prochain ID disponible» peut être délicate avec les doubles et nécessite une certaine connaissance de l'arithmétique à 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 dit «JVM conforme aux normes», je voulais exclure certaines implémentations de JVM endommagées par le cerveau. Regarde ça .

quant_dev
la source
Lorsque vous utilisez des flottants comme identifiants, il faut faire attention soit de s'assurer qu'ils sont comparés en utilisant ==plutôt que de equals, soit de s'assurer qu'aucun flottant qui se compare inégal à lui-même n'est stocké dans une table. Sinon, un programme qui essaie, par exemple, de compter combien de résultats uniques peuvent être produits à partir d'une expression lorsqu'il est alimenté par diverses entrées, peut considérer chaque valeur NaN comme unique.
supercat du
Ce qui précède fait référence à Float, pas à float.
quant_dev
De quoi parle-t-on Float? Si l'on essaie de construire une table de floatvaleurs uniques et de les comparer avec ==, les horribles règles de comparaison IEEE-754 entraîneront l'inondation de la table avec des NaNvaleurs.
supercat du
floatle type n'a pas de equalsméthode.
quant_dev
Ah - je ne parlais pas d'une equalsméthode d'instance, mais plutôt de la méthode statique (je pense au sein de la Floatclasse) qui compare deux valeurs de type float.
supercat du
9

Il s'agit d'un problème non spécifique à java. L'utilisation de == pour comparer deux flottants / doubles / tout nombre de type décimal peut potentiellement causer des problèmes en raison de la façon dont ils sont stockés. Un float simple précision (selon la norme IEEE 754) a 32 bits, répartis comme suit:

1 bit - Signe (0 = positif, 1 = négatif)
8 bits - Exposant (une représentation spéciale (biais-127) du x en 2 ^ x)
23 bits - Mantisa. Le numéro d'actuall enregistré.

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), l'IEEE a décidé que le 1 devait être supposé. Par exemple, une mantisa de 1011 vaut vraiment 1.1011.

Cela peut causer des problèmes de comparaison, en particulier avec 0 car 0 ne peut pas être représenté exactement dans un flottant. C'est la principale raison pour laquelle le == est déconseillé, 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 que le langage est universel sur de nombreuses plates-formes différentes, chacune d'entre elles pouvant avoir son propre format flottant unique. Cela rend encore plus important d'éviter ==.

La bonne façon de comparer deux flottants (non spécifique à la langue, pensez à l'égalité) pour l'égalité est la suivante:

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

où ACCEPTABLE_ERROR est #defined ou une autre constante égale à 0,00000000001 ou toute précision requise, comme Victor l'a déjà mentionné.

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

CodeFusionMobile
la source
3
Java a un comportement défini pour les flottants. Cela ne dépend pas de la plate-forme.
Yishai
9

À partir d'aujourd'hui, le moyen rapide et facile de le faire est:

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

Cependant, la documentation ne spécifie pas clairement la valeur de la différence de marge (un epsilon de la réponse de @Victor) qui est toujours présente dans les calculs sur les flottants, mais cela devrait être quelque chose de raisonnable car il fait partie de la bibliothèque de langage 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.

Alisa
la source
1
Les documents que vous avez liés indiquent «la valeur 0 si f1 est numériquement égale à f2», ce qui en fait la même chose que faire (sectionId == currentSectionId)ce qui n'est pas précis pour les virgules flottantes. la méthode epsilon est la meilleure approche, qui se trouve dans cette réponse: stackoverflow.com/a/1088271/4212710
typoerrpr
8

Les valeurs des points de Foating ne sont pas fiables, en raison d'une erreur d'arrondi.

En tant que tels, ils ne devraient probablement pas être utilisés comme valeurs de clé, telles que sectionID. Utilisez plutôt des entiers, ou longsi intne contient pas suffisamment de valeurs possibles.

Eric Wilson
la source
2
D'accord. Étant donné que ce sont des ID, il n'y a aucune raison de compliquer les choses avec l'arithmétique en virgule flottante.
Yohnny le
2
Ou un long. Selon le nombre d'identifiants uniques générés à l'avenir, un int peut ne pas être assez grand.
Wayne Hartman
Quelle est la précision du double par rapport au flotteur?
Arvindh Mani
1
Les @ArvindhMani doublesont beaucoup plus précis, mais ce sont aussi des valeurs à virgule flottante, donc ma réponse était censée inclure à la fois floatet double.
Eric Wilson
7

En plus des réponses précédentes, vous devez être conscient qu'il existe des comportements étranges associés à -0.0fet +0.0f(ils le sont ==mais pas equals) et Float.NaN(c'est equalsmais pas ==) (j'espère que j'ai bien compris - argh, ne le faites 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.

Tom Hawtin - Tacle
la source
Si quelque chose est ==, alors ils sont identiques jusqu'au bit. Comment pourraient-ils ne pas être égaux ()? Peut-être que vous l'avez à l'envers?
mkb
@Matt NaN est spécial. Double.isNaN (double x) en Java est en fait implémenté comme {return x! = X; } ...
quant_dev
1
Avec des flottants, ==cela ne signifie pas que les nombres sont "identiques au bit" (le même nombre peut être représenté avec différents modèles de bits, bien qu'un seul d'entre eux soit de forme normalisée). De plus, -0.0fet 0.0fsont représentés par différents modèles de bits (le bit de signe est différent), mais comparez comme égal à ==(mais pas avec equals). Votre hypothèse de ==comparaison au niveau du bit est, d'une manière générale, erronée.
Pavel Minaev
5

Vous pouvez utiliser Float.floatToIntBits ().

Float.floatToIntBits(sectionID) == Float.floatToIntBits(currentSectionID)
aamadmi
la source
1
Tu es sur la bonne piste. floatToIntBits () est la bonne voie à suivre, mais il serait plus facile d'utiliser simplement la fonction intégrée equals () de Float. Voir ici: stackoverflow.com/a/3668105/2066079 . Vous pouvez voir que la valeur par défaut equals () utilise floatToIntBits en interne.
dberm22
1
Oui s'il s'agit d'objets Float. Vous pouvez utiliser l'équation ci-dessus pour les primitives.
aamadmi le
4

Tout d'abord, sont-ils flottants ou flottants? Si l'un d'eux est un Float, vous devez utiliser la méthode equals (). De plus, il est probablement préférable d'utiliser la méthode statique Float.compare.

omerkudat
la source
4

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 sûr, vous pouvez choisir plus ou moins de 5 ULP («unité à la dernière place»).

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

Michael Piefel
la source
lors du changement de float en double, cette méthode ne fonctionne pas comme isDoubleEqual (0.1 + 0.2-0.3, 0.0) == false
hychou
Il semble que vous ayez besoin de plus de 10_000_000_000_000_000L comme facteur pour doublecouvrir cela.
Michael Piefel
3

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

KM.
la source
2

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

VoiceOfUnreason
la source
2

Traitez-vous du 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 assez ennuyeuse." Comment? Comment feriez-vous mieux? Il y a certains nombres qui ne peuvent être représentés correctement dans aucune base, car ils ne finissent jamais. Pi est un bon exemple. Vous ne pouvez que l'approcher. Si vous avez une meilleure solution, contactez Intel.

xcramps
la source
1

Comme mentionné dans d'autres réponses, les doubles peuvent avoir de petits écarts. Et vous pouvez écrire votre propre méthode pour les comparer en utilisant un écart «acceptable». Cependant ...

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

Il contient quelques constantes intéressantes: SAFE_MINet EPSILON, qui sont les écarts maximums possibles des opérations arithmétiques simples.

Il fournit également les méthodes nécessaires pour comparer, égaler ou arrondir les doubles. (en utilisant des ulps ou une déviation absolue)

bvdb
la source
1

En une seule ligne de réponse, je peux dire, vous devriez utiliser:

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

Pour vous en apprendre davantage sur l'utilisation correcte des opérateurs associés, j'élabore ici quelques cas: En général, il existe trois façons de tester des 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 signifie savoir si les deux objets sont identiques. D'autre part, .equals () teste si les deux chaînes sont logiquement de valeur égale. Enfin, Objects.equals () teste les valeurs nulles dans les deux chaînes puis détermine s'il faut appeler .equals ().

Opérateur idéal à utiliser

Eh bien, cela a fait l'objet de nombreux débats car chacun des trois opérateurs a son ensemble unique de forces et de faiblesses. Exemple, == est souvent une option préférée lors de la comparaison de la référence d'objet, mais il y a des cas où il peut sembler comparer des valeurs de chaîne également.

Cependant, ce que vous obtenez est une valeur de baisse parce que Java crée l'illusion que vous comparez des valeurs mais que vous ne l'êtes pas vraiment. Considérez 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 les développeurs Web optent donc pour cela.

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

Thèmes courants
la source
-2

La bonne façon serait

java.lang.Float.compare(float1, float2)
Eric
la source
7
Float.compare (float1, float2) renvoie un int, donc il ne peut pas être utilisé à la place de float1 == float2 dans la condition if. De plus, cela ne résout pas vraiment le problème sous-jacent auquel cet avertissement fait référence - que si les flottants sont les résultats d'un calcul numérique, float1! = Float2 peut se produire uniquement à cause d'erreurs d'arrondi.
quant_dev
1
à droite, vous ne pouvez pas copier-coller, vous devez d'abord vérifier le document.
Eric
2
Ce que vous pouvez faire à la place de float1 == float2 est Float.compare (float1, float2) == 0.
deterb
29
Cela ne vous achète rien - vous obtenez toujoursFloat.compare(1.1 + 2.2, 3.3) != 0
Pavel Minaev
-2

Une façon de réduire l'erreur d'arrondi consiste à utiliser double plutôt que float. Cela ne résoudra pas le problème, mais cela réduira le nombre d'erreurs dans votre programme et float n'est presque jamais le meilleur choix. A MON HUMBLE AVIS.

Peter Lawrey
la source