Est-il sûr de vérifier les valeurs à virgule flottante pour l'égalité à 0?

100

Je sais que vous ne pouvez pas compter sur l'égalité entre les valeurs de type double ou décimal normalement, mais je me demande si 0 est un cas particulier.

Bien que je puisse comprendre les imprécisions entre 0,0000000000000001 et 0,0000000000000002, 0 lui-même semble assez difficile à gâcher car ce n'est rien. Si vous n'êtes imprécis sur rien, ce n'est plus rien.

Mais je ne connais pas grand-chose à ce sujet donc ce n'est pas à moi de le dire.

double x = 0.0;
return (x == 0.0) ? true : false;

Cela reviendra-t-il toujours vrai?

Gene Roberts
la source
69
L'opérateur ternaire est redondant dans ce code :)
Joel Coehoorn
5
LOL tu as raison. Allez-moi
Gene Roberts
Je ne le ferais pas parce que vous ne savez pas comment x a été mis à zéro. Si vous voulez toujours le faire, vous voudrez probablement arrondir ou plancher x pour vous débarrasser du 1e-12 ou autre qui pourrait être marqué à la fin.
Rex Logan

Réponses:

115

Il est prudent de s'attendre à ce que la comparaison retourne truesi et seulement si la variable double a une valeur de exactement 0.0(ce qui dans votre extrait de code d'origine est, bien sûr, le cas). Ceci est cohérent avec la sémantique de l' ==opérateur. a == bsignifie " aest égal à b".

Il n'est pas sûr (car ce n'est pas correct ) de s'attendre à ce que le résultat d'un calcul soit nul en arithmétique double (ou plus généralement en virgule flottante) chaque fois que le résultat du même calcul en mathématiques pures est nul. En effet, lorsque les calculs entrent dans le sol, une erreur de précision en virgule flottante apparaît - un concept qui n'existe pas dans l'arithmétique des nombres réels en mathématiques.

Daniel Daranas
la source
51

Si vous avez besoin de faire beaucoup de comparaisons "d'égalité", il peut être judicieux d'écrire une petite fonction d'aide ou une méthode d'extension dans .NET 3.5 pour comparer:

public static bool AlmostEquals(this double double1, double double2, double precision)
{
    return (Math.Abs(double1 - double2) <= precision);
}

Cela pourrait être utilisé de la manière suivante:

double d1 = 10.0 * .1;
bool equals = d1.AlmostEquals(0.0, 0.0000001);
Dirk Vollmar
la source
4
Vous pourriez avoir une erreur d'annulation soustractive en comparant double1 et double2, au cas où ces nombres auraient des valeurs très proches les unes des autres. Je supprimerais Math.Abs ​​et vérifierais chaque branche individuellement d1> = d2 - e et d1 <= d2 + e
Theodore Zographos
"Étant donné qu'Epsilon définit l'expression minimale d'une valeur positive dont la plage est proche de zéro, la marge de différence entre deux valeurs similaires doit être supérieure à Epsilon. En général, elle est plusieurs fois supérieure à Epsilon. Pour cette raison, nous vous recommandons de le faire n'utilisez pas Epsilon lors de la comparaison des valeurs Double pour l'égalité. " - msdn.microsoft.com/en-gb/library/ya2zha7s(v=vs.110).aspx
Rafael Costa
15

Pour votre échantillon simple, ce test est correct. Mais qu'en est-il de ceci:

bool b = ( 10.0 * .1 - 1.0 == 0.0 );

N'oubliez pas que .1 est une décimale répétitive en binaire et ne peut pas être représenté exactement. Ensuite, comparez cela à ce code:

double d1 = 10.0 * .1; // make sure the compiler hasn't optimized the .1 issue away
bool b = ( d1 - 1.0 == 0.0 );

Je vous laisse faire un test pour voir les résultats réels: vous aurez plus de chances de vous en souvenir de cette façon.

Joel Coehoorn
la source
5
En fait, cela retourne vrai pour une raison quelconque (au moins dans LINQPad).
Alexey Romanov
Quel est le "problème .1" dont vous parlez?
Teejay
14

À partir de l'entrée MSDN pour Double.Equals :

Précision dans les comparaisons

La méthode Equals doit être utilisée avec prudence, car deux valeurs apparemment équivalentes peuvent être inégales en raison de la précision différente des deux valeurs. L'exemple suivant indique que la valeur Double .3333 et le Double renvoyé en divisant 1 par 3 sont inégaux.

...

Plutôt que de comparer pour l'égalité, une technique recommandée consiste à définir une marge de différence acceptable entre deux valeurs (comme 0,01% de l'une des valeurs). Si la valeur absolue de la différence entre les deux valeurs est inférieure ou égale à cette marge, la différence est probablement due à des différences de précision et, par conséquent, les valeurs sont susceptibles d'être égales. L'exemple suivant utilise cette technique pour comparer 0,33333 et 1/3, les deux valeurs Double que l'exemple de code précédent a trouvé inégales.

Voir également Double.Epsilon .

Stu Mackellar
la source
1
Il est également possible que des valeurs pas tout à fait équivalentes se comparent comme égales. On pourrait penser que si x.Equals(y), alors (1/x).Equals(1/y), mais ce n'est pas le cas si xest 0et yest 1/Double.NegativeInfinity. Ces valeurs sont déclarées égales, même si leurs réciproques ne le sont pas.
supercat du
@supercat: Ils sont équivalents. Et ils n'ont pas de réciproque. Vous pouvez exécuter à nouveau votre test avec x = 0et y = 0, et vous le trouverez toujours 1/x != 1/y.
Ben Voigt
@BenVoigt: Avec xet ycomme type double? Comment comparez-vous les résultats pour les rendre inégaux? Notez que 1 / 0.0 n'est pas NaN.
supercat
@supercat: Ok, c'est l'une des choses que IEEE-754 se trompe. (Premièrement, cela 1.0/0.0ne parvient pas à être NaN comme il se doit, car la limite n'est pas unique. Deuxièmement, que les infinis se comparent égaux les uns aux autres, sans prêter aucune attention aux degrés d'infini)
Ben Voigt
@BenVoigt: Si le zéro était le résultat de la multiplication de deux très petits nombres, alors diviser 1,0 en cela devrait donner une valeur qui compare plus grand que n'importe quel nombre de petits nombres avait le même signe, et moins que n'importe quel nombre si l'un des petits les nombres avaient des signes opposés. IMHO, IEEE-754 serait mieux s'il avait un zéro non signé, mais des infinitésimales positives et négatives.
supercat
6

Le problème vient lorsque vous comparez différents types d'implémentation de valeur en virgule flottante, par exemple en comparant float avec double. Mais avec le même type, cela ne devrait pas être un problème.

float f = 0.1F;
bool b1 = (f == 0.1); //returns false
bool b2 = (f == 0.1F); //returns true

Le problème est que le programmeur oublie parfois que le cast de type implicite (double en float) se produit pour la comparaison et cela entraîne un bogue.

Yogee
la source
3

Si le nombre a été directement attribué au flottant ou au double, il est alors sûr de tester par rapport à zéro ou à tout nombre entier pouvant être représenté en 53 bits pour un double ou 24 bits pour un flottant.

Ou pour le dire autrement, vous pouvez toujours attribuer une valeur entière à un double, puis comparer le double au même entier et être assuré qu'il sera égal.

Vous pouvez également commencer par attribuer un nombre entier et faire en sorte que les comparaisons simples continuent de fonctionner en vous contentant d'ajouter, de soustraire ou de multiplier par des nombres entiers (en supposant que le résultat est inférieur à 24 bits pour un flottant et 53 bits pour un double). Ainsi, vous pouvez traiter les flottants et les doubles comme des entiers dans certaines conditions contrôlées.

Kevin Gale
la source
Je suis d'accord avec votre déclaration en général (et je l'ai votée) mais je crois que cela dépend vraiment si l'implémentation en virgule flottante IEEE 754 est utilisée ou non. Et je crois que chaque ordinateur "moderne" utilise IEEE 754, au moins pour le stockage des flotteurs (il y a des règles d'arrondi étranges qui diffèrent).
Mark Lakata
2

Non, ce n'est pas OK. Les valeurs dites dénormalisées (sous-normales), lorsqu'elles sont comparées à 0,0, seraient comparées comme fausses (non nulles), mais lorsqu'elles sont utilisées dans une équation, elles seraient normalisées (devenant 0,0). Ainsi, l'utiliser comme mécanisme pour éviter une division par zéro n'est pas sûr. Au lieu de cela, ajoutez 1.0 et comparez à 1.0. Cela garantira que toutes les sous-normales sont traitées comme nulles.


la source
Les sous-normaux sont également connus sous le nom de dénormalités
Manuel
Les sous-normales ne deviennent pas égales à zéro lorsqu'elles sont utilisées, bien qu'elles puissent ou non produire le même résultat en fonction de l'opération exacte.
wnoise
-2

Essayez ceci, et vous constaterez que == n'est pas fiable pour double / float.
double d = 0.1 + 0.2; bool b = d == 0.3;

Voici la réponse de Quora.

Rickyuu
la source
-4

En fait, je pense qu'il est préférable d'utiliser les codes suivants pour comparer une valeur double à 0,0:

double x = 0.0;
return (Math.Abs(x) < double.Epsilon) ? true : false;

Idem pour le flotteur:

float x = 0.0f;
return (Math.Abs(x) < float.Epsilon) ? true : false;
David.Chu.ca
la source
5
Non. D'après la documentation de double.Epsilon: "Si vous créez un algorithme personnalisé qui détermine si deux nombres à virgule flottante peuvent être considérés comme égaux, vous devez utiliser une valeur supérieure à la constante d'Epsilon pour établir la marge de différence absolue acceptable pour que les deux valeurs soient considérées comme égales. (En règle générale, cette marge de différence est plusieurs fois supérieure à Epsilon.) "
Alastair Maw
1
@AlastairMaw cela s'applique à la vérification de l'égalité de deux doubles de n'importe quelle taille. Pour vérifier l'égalité à zéro, double.Epsilon est très bien.
jwg
4
Non, ça ne l' est pas . Il est très probable que la valeur à laquelle vous êtes parvenu via un calcul soit plusieurs fois éloignée de zéro par epsilon, mais devrait toujours être considérée comme zéro. Vous n'obtenez pas comme par magie un tas de précision supplémentaire dans votre résultat intermédiaire de quelque part, simplement parce qu'il se trouve être proche de zéro.
Alastair Maw
4
Par exemple: (1.0 / 5.0 + 1.0 / 5.0 - 1.0 / 10.0 - 1.0 / 10.0 - 1.0 / 10.0 - 1.0 / 10.0) <double.Epsilon == false (et considérablement en termes d'ampleur: 2.78E-17 vs 4.94E -324)
Alastair Maw
alors, quelle est la précision recommandée, si double.Epsilon n'est pas ok? Est-ce que 10 fois de epsilon ok? 100 fois?
liang