La bonne façon de comparer un System.Double à '0' (un nombre, int?)

87

Désolé, c'est peut-être une question stupide facile, mais j'ai besoin de savoir pour être sûr.

J'ai cette ifexpression,

void Foo()
{
    System.Double something = GetSomething();
    if (something == 0) //Comparison of floating point numbers with equality 
                     // operator. Possible loss of precision while rounding value
        {}
}

Cette expression est-elle égale à

void Foo()
{
    System.Double something = GetSomething();
    if (something < 1)
        {}
}

? Parce qu'alors je pourrais avoir un problème, en entrant le ifavec par exemple une valeur de 0,9.

radbyx
la source
3
// Comparison of floating point numbers with equality // operator. Aviez-vous vraiment besoin de le préciser? :)
George Johnston
1
Heck non. Il y a énormément de valeurs entre 0 et 1. Pourquoi ne pas simplement le tester et voir par vous-même?
Igby Largeman
12
J'ai juste écrit la même chose que Resharper, pour montrer où je me concentre.
radbyx
@Charles: De plus, il y a beaucoup de nombres inférieurs à 0.
Brian
duplication possible de la comparaison de valeurs doubles en C #
Dzyann

Réponses:

114

Eh bien, à quel point avez-vous besoin que la valeur soit proche de 0? Si vous effectuez un grand nombre d'opérations en virgule flottante qui, en "précision infinie", peuvent donner 0, vous pourriez vous retrouver avec un résultat "très proche" de 0.

En règle générale, dans cette situation, vous souhaitez fournir une sorte d'epsilon et vérifier que le résultat se trouve juste dans cet epsilon:

if (Math.Abs(something) < 0.001)

L'epsilon que vous devez utiliser est spécifique à l'application - cela dépend de ce que vous faites.

Bien sûr, si le résultat doit être exactement zéro, alors un simple contrôle d'égalité convient.

Jon Skeet
la source
En fait, j'ai besoin que ce soit exactement zéro, à quoi cela ressemblerait-il? J'ai cherché Double.Zero, mais je connais la chance. Il y a une constante non? Btw, merci, je reçois la partie epsilon maintenant :)
radbyx
24
@radbyx: Utilisez simplement == 0. Vous avez un littéral là - c'est assez constant :)
Jon Skeet
35

Si somethinga été attribué à partir du résultat d'une opération autre que, something = 0il vaut mieux utiliser:

if(Math.Abs(something) < Double.Epsilon)
{
//do something
}

Edit : Ce code est faux. Epsilon est le plus petit nombre, mais pas tout à fait zéro. Lorsque vous souhaitez comparer un nombre à un autre, vous devez réfléchir à la tolérance acceptable. Disons que tout ce qui dépasse .00001 ne vous préoccupe pas. C'est le numéro que vous utiliseriez. La valeur dépend du domaine. Cependant, ce n'est certainement jamais Double.Epsilon.

sonatique
la source
2
Cela ne résout pas le problème d' arrondi, par exemple Math.Abs(0.1f - 0.1d) < double.Epsilonestfalse
Thomas Lulé
6
Double.Epsilon est trop petit pour une telle comparaison. Double.Epsilon est le plus petit nombre positif que double puisse représenter.
Evgeni Nabokov
3
Cela n'a aucun sens car c'est pratiquement la même chose que de comparer avec 0. -1
Gaspa79
1
Le but est de comparer avec le concept de 0 sans utiliser ==. Au moins ça fait sens mathématiquement. Je suppose que vous avez un double à portée de main et que vous voulez le comparer au concept de zéro sans ==. Si votre double est différent de 0d pour une raison quelconque, y compris l'arrondi, le test de remplissage renvoie faux. Cette comparaison semble valable pour tout double et ne retournera vrai que si ce double est le plus petit que le plus petit nombre pouvant être représenté, ce qui semble une bonne définition pour tester le concept de 0, non?
sonatique
3
@MaurGi: vous vous trompez: double d = Math.Sqrt(10100)*2; double a = Math.Sqrt(40400); if(Math.Abs(a - d) < double.Epsilon) { Console.WriteLine("true"); }
sonatique
26

Votre somethingest un double, et vous l'avez correctement identifié dans la ligne

if (something == 0)

nous avons un doublesur le côté gauche (lhs) et un intsur le côté droit (rhs).

Mais maintenant, il semble que vous pensez que le lhs sera converti en un int, puis le ==signe comparera deux entiers. Ce n'est pas ce qui arrive. La conversion de double à int est explicite et ne peut pas se faire "automatiquement".

Au lieu de cela, le contraire se produit. Le rhs est converti en double, puis le ==signe devient un test d'égalité entre deux doubles. Cette conversion est implicite (automatique).

Il est préférable (par certains) d'écrire

if (something == 0.0)

ou

if (something == 0d)

car alors c'est immédiat que vous comparez deux doubles. Cependant, ce n'est qu'une question de style et de lisibilité car le compilateur fera la même chose dans tous les cas.

Il est également pertinent, dans certains cas, d'introduire une «tolérance» comme dans la réponse de Jon Skeet, mais cette tolérance en serait une doubleaussi. Cela pourrait bien sûr l'être 1.0si vous le vouliez, mais il n'est pas nécessaire que ce soit l'entier [le moins strictement positif].

Jeppe Stig Nielsen
la source
17

Si vous souhaitez simplement supprimer l'avertissement, procédez comme suit:

if (something.Equals(0.0))

Bien sûr, ce n'est une solution valable que si vous savez que la dérive n'est pas un problème. Je fais souvent cela pour vérifier si je suis sur le point de diviser par zéro.

Russell Phillips
la source
4

Je ne pense pas que ce soit égal, honnêtement. Prenons votre propre exemple: quelque chose = 0,9 ou 0,0004. Dans le premier cas, ce sera FALSE, dans le second cas, ce sera VRAI. En traitant avec ces types, je définis généralement pour moi un pourcentage de précision et je compare dans cette précision. Dépend de vos besoins. quelque chose comme...

if(((int)(something*100)) == 0) {


//do something
}

J'espère que cela t'aides.

Tigran
la source
2
quelque chose doit être exactement nul.
radbyx
donc vous êtes chanceux, dans ce cas :)
Tigran
3

Voici l'exemple présentant le problème (préparé dans LinQPad - si vous ne l'avez pas, utilisez simplement à la Console.Writelineplace de la Dumpméthode):

void Main()
{
    double x = 0.000001 / 0.1;
    double y = 0.001 * 0.01; 

    double res = (x-y);
    res.Dump();
    (res == 0).Dump();
}

Les deux x et y sont théoriquement identiques et égaux à: 0,00001 mais en raison du manque de «précision infinie», ces valeurs sont légèrement différentes. Malheureusement un peu assez pour revenir falseen comparant à 0 de manière habituelle.

michal-fou
la source