web-dev-qa-db-fra.com

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

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 spécial.

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

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

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

Est-ce que cela reviendra toujours vrai?

93
Gene Roberts

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

Il est pas sûr (parce que c'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.

109
Daniel Daranas

Si vous avez besoin de faire beaucoup de comparaisons "d'égalité", il peut être judicieux d'écrire une petite fonction d'assistance 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);
49
Dirk Vollmar

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

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. Comparez ensuite 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 êtes plus susceptible de vous en souvenir de cette façon.

15
Joel Coehoorn

À 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 signale que la valeur Double .3333 et la valeur Double retournée en divisant 1 par 3 sont inégales.

...

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 .33333 et 1/3, les deux valeurs Double que l'exemple de code précédent a trouvées inégales.

Voir aussi Double.Epsilon .

13
Stu Mackellar

Le problème survient lorsque vous comparez différents types d'implémentation de valeurs à virgule flottante, par exemple comparer flotteur avec double. Mais avec le même type, cela ne devrait pas poser de 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 la conversion de type implicite (double pour flotter) se produit pour la comparaison et cela se traduit par un bogue.

6
Yogee

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

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 ajoutant, soustrayant ou multipliant par des nombres entiers (en supposant que le résultat est inférieur à 24 bits pour un flottant et 53 bits pour un double). Vous pouvez donc traiter les flottants et les doubles comme des entiers dans certaines conditions contrôlées.

3
Kevin Gale

Non, ce n'est pas OK. Les valeurs dites dénormalisées (subnormales), comparées à 0,0, se comparent comme fausses (non nulles), mais lorsqu'elles sont utilisées dans une équation, elles sont normalisées (deviennent 0,0). Ainsi, l'utiliser comme un mécanisme pour éviter une division par zéro n'est pas sûr. Ajoutez plutôt 1.0 et comparez à 1.0. Cela garantira que toutes les sous-normales sont traitées comme nulles.

2
Bob job