Est-il prudent de vérifier que les valeurs en virgule flottante sont égales à 0?
-
20-08-2019 - |
Question
Je sais que vous ne pouvez pas compter normalement sur l'égalité entre les valeurs de type double ou décimale, 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 ê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?
La solution
Il est sûr de s'attendre à ce que la comparaison retourne true
si et seulement si la variable double a exactement la valeur 0.0
(ce qui, dans votre extrait de code d'origine, est bien sûr: l'affaire). Ceci est cohérent avec la sémantique de l'opérateur ==
. a == b
signifie que " a
est égal à b
".
Il est dangereux (car inexact ) de s'attendre à ce que le résultat de certains calculs soit nul dans l'arithmétique double (ou plus généralement, à virgule flottante). chaque fois que le résultat du même calcul en mathématiques pures est égal à zéro. 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.
Autres conseils
Si vous devez faire beaucoup de " égalité " comparaisons, 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);
}
Ceci pourrait être utilisé de la manière suivante:
double d1 = 10.0 * .1;
bool equals = d1.AlmostEquals(0.0, 0.0000001);
Pour votre exemple simple, ce test est correct. Mais qu'en est-il de cela:
bool b = ( 10.0 * .1 - 1.0 == 0.0 );
N'oubliez pas que .1 est un nombre décimal répété 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 laisserai faire un test pour voir les résultats réels: vous vous en souviendrez probablement mieux.
À 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 apparemment les valeurs équivalentes peuvent être inégales en raison à la précision différente des deux valeurs. Les exemples suivants de rapports que la valeur double .3333 et la Double retourné en divisant 1 par 3 sont inégale.
...
Plutôt que de comparer pour l'égalité, une technique recommandée implique définir une marge acceptable de différence entre deux valeurs (telles que .01% d'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 susceptible d'être due à des différences de la précision et, par conséquent, les valeurs sont susceptibles d'être égaux. Le suivant exemple utilise cette technique pour comparer .33333 et 1/3, les deux valeurs de Double que l'exemple de code précédent trouvé être inégale.
Voir également Double.Epsilon .
Le problème survient lorsque vous comparez différents types d'implémentation de valeurs en virgule flottante, par exemple. comparer 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 transtypage de type implicite (double à float) est en cours pour la comparaison et qu'il en résulte un bogue.
Si le numéro a été directement attribué au nombre à virgule flottante ou au numéro double, il est prudent de tester le nombre zéro ou tout nombre entier pouvant être représenté sur 53 bits pour un nombre double ou 24 bits pour un nombre à virgule flottante.
Ou, autrement dit, vous pouvez toujours attribuer une valeur entière à un double, puis comparer le double au même nombre entier et vous assurer qu'il sera égal.
Vous pouvez également commencer par attribuer un nombre entier et faire en sorte que des comparaisons simples continuent de s’en tenir à l’ajout, la soustraction ou la multiplication par des nombres entiers (en supposant que le résultat est inférieur à 24 bits pour un float et à 53 bits pour un double) . Vous pouvez donc traiter les flottants et les doublons comme des entiers dans certaines conditions contrôlées.
Non, ce n'est pas OK. Les valeurs dites dénormalisées (sous-normales), lorsqu'elles sont comparées à 0,0, sont considérées comme fausses (non nulles), mais lorsqu'elles sont utilisées dans une équation, elles sont normalisées (deviennent 0,0). Par conséquent, l’utilisation de ce mécanisme pour éviter une division par zéro n’est pas sans danger. Au lieu de cela, ajoutez 1.0 et comparez à 1.0. Cela garantira que toutes les sous-normales sont traitées comme zéro.
Essayez ceci et vous constaterez que == n’est pas fiable pour le double / float.
double d = 0.1 + 0.2;
bool b = d == 0.3;
Voici les réponse de Quora.
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 float:
float x = 0.0f;
return (Math.Abs(x) < float.Epsilon) ? true : false;