Der richtige Weg, um ein System zu vergleichen. Doppel mit '0' (eine Zahl, int?)

86

Entschuldigung, das mag eine einfache, dumme Frage sein, aber ich muss es wissen, um sicher zu sein.

Ich habe diesen ifAusdruck,

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

Ist dieser Ausdruck gleich mit

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

? Denn dann könnte ich ein Problem haben, das ifmit zB einem Wert von 0,9 einzugeben.

Radbyx
quelle
3
// Comparison of floating point numbers with equality // operator. Mussten Sie das wirklich angeben? :)
George Johnston
1
Mist nein. Es gibt verdammt viele Werte zwischen 0 und 1. Warum testen Sie es nicht einfach und überzeugen Sie sich selbst?
Igby Largeman
12
Ich habe genauso geschrieben wie Resharper, um zu zeigen, wo mein Fokus liegt.
Radbyx
@ Charles: Außerdem gibt es viele Zahlen, die kleiner als 0 sind.
Brian
Mögliches Duplikat des Vergleichs von Doppelwerten in C #
Dzyann

Antworten:

114

Nun, wie nahe muss der Wert bei 0 liegen? Wenn Sie viele Gleitkommaoperationen durchlaufen, die bei "unendlicher Genauigkeit" zu 0 führen können, erhalten Sie möglicherweise ein Ergebnis "sehr nahe" an 0.

In der Regel möchten Sie in dieser Situation eine Art Epsilon bereitstellen und überprüfen, ob das Ergebnis genau innerhalb dieses Epsilons liegt:

if (Math.Abs(something) < 0.001)

Das Epsilon, das Sie verwenden sollten, ist anwendungsspezifisch - es hängt davon ab, was Sie tun.

Wenn das Ergebnis genau Null sein sollte, ist eine einfache Gleichheitsprüfung natürlich in Ordnung.

Jon Skeet
quelle
Eigentlich muss es genau Null sein, wie würde das aussehen? Ich habe nach Double.Zero gesucht, weiß aber Glück. Es gibt eine Konstante, oder? Übrigens, danke, ich bekomme jetzt den Epsilon-Teil :)
Radbyx
24
@radbyx: Einfach benutzen == 0. Sie haben dort ein Literal - das ist ziemlich konstant :)
Jon Skeet
34

Wenn somethingdas Ergebnis einer anderen Operation als der zugewiesen wurde, verwenden something = 0Sie besser:

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

Bearbeiten : Dieser Code ist falsch. Epsilon ist die kleinste Zahl, aber nicht ganz Null. Wenn Sie eine Zahl mit einer anderen Zahl vergleichen möchten, müssen Sie sich überlegen, welche Toleranz akzeptabel ist. Nehmen wir an, dass alles, was über .00001 hinausgeht, Sie nicht interessiert. Das ist die Nummer, die Sie verwenden würden. Der Wert hängt von der Domain ab. Es ist jedoch meistens nie Double.Epsilon.

Sonatique
quelle
2
Dies löst nicht das Rundungsproblem, zum Beispiel Math.Abs(0.1f - 0.1d) < double.Epsilonistfalse
Thomas Lulé
6
Double.Epsilon ist zu klein für einen solchen Vergleich. Double.Epsilon ist die kleinste positive Zahl, die double darstellen kann.
Evgeni Nabokov
3
Dies macht keinen Sinn, da es praktisch das gleiche ist wie ein Vergleich mit 0. -1
Gaspa79
1
Das Ziel ist es, mit dem Konzept von 0 zu vergleichen, ohne == zu verwenden. Zumindest macht es mathematisch Sinn. Ich gehe davon aus, dass Sie ein Double zur Hand haben und es mit dem Konzept der Null ohne == vergleichen möchten. Wenn sich Ihr Double aus irgendeinem Grund von 0d unterscheidet, einschließlich Rundung, gibt die Testfüllung false zurück. Dieser Vergleich scheint für jedes Double gültig zu sein und gibt nur dann true zurück, wenn dieses Double klein ist als die kleinste Zahl, die dargestellt werden kann. Dies scheint eine gute Definition für das Testen des Konzepts von 0 zu sein, nein?
Sonatique
3
@ MaurGi: Sie sind falsch: double d = Math.Sqrt(10100)*2; double a = Math.Sqrt(40400); if(Math.Abs(a - d) < double.Epsilon) { Console.WriteLine("true"); }
Sonatique
25

Ihr somethingist ein double, und Sie haben das in der Zeile richtig identifiziert

if (something == 0)

Wir haben eine doubleauf der linken Seite (lhs) und eine intauf der rechten Seite (rhs).

Aber jetzt scheint es so, als ob Sie denken, dass die lhs in eine konvertiert werden int, und dann ==vergleicht das Vorzeichen zwei ganze Zahlen. Das ist nicht , was passiert. Die Konvertierung von double nach int ist explizit und kann nicht "automatisch" erfolgen.

Stattdessen passiert das Gegenteil. Das rhs wird in konvertiert double, und dann wird das ==Vorzeichen zu einem Gleichheitstest zwischen zwei Doppelwerten. Diese Konvertierung ist implizit (automatisch).

Es wird (von einigen) als besser angesehen, zu schreiben

if (something == 0.0)

oder

if (something == 0d)

denn dann ist es sofort, dass Sie zwei Doppel vergleichen. Dies ist jedoch nur eine Frage des Stils und der Lesbarkeit, da der Compiler auf jeden Fall das Gleiche tut.

In einigen Fällen ist es auch relevant, eine "Toleranz" wie in Jon Skeets Antwort einzuführen, aber diese Toleranz wäre auch eine double. Es könnte natürlich sein, 1.0wenn Sie wollten, aber es muss nicht [die am wenigsten streng positive] Ganzzahl sein.

Jeppe Stig Nielsen
quelle
17

Wenn Sie die Warnung einfach unterdrücken möchten, gehen Sie folgendermaßen vor:

if (something.Equals(0.0))

Dies ist natürlich nur dann eine gültige Lösung, wenn Sie wissen, dass Drift kein Problem darstellt. Ich mache das oft, um zu überprüfen, ob ich durch Null teilen werde.

Russell Phillips
quelle
4

Ich glaube nicht, dass es gleich ist, ehrlich. Betrachten Sie Ihr eigenes Beispiel: etwas = 0,9 oder 0,0004. Im ersten Fall ist es FALSE, im zweiten Fall ist es TRUE. Der Umgang mit diesen Typen definiere ich normalerweise für mich Präzisionsprozentsatz und vergleiche innerhalb dieser Präzision. Kommt auf deine Bedürfnisse an. etwas wie...

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


//do something
}

Hoffe das hilft.

Tigran
quelle
2
etwas muss genau null sein.
Radbyx
Also bist du ein Glückspilz, in diesem Fall :)
Tigran
3

Hier ist das Beispiel, das das Problem darstellt (vorbereitet in LinQPad - wenn Sie es nicht haben, verwenden Sie es einfach Console.Writelineanstelle der DumpMethode):

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

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

Sowohl x als auch y sind theoretisch gleich und gleich: 0,00001, aber aufgrund mangelnder "unendlicher Genauigkeit" unterscheiden sich diese Werte geringfügig. Leider etwas genug, um falsebeim Vergleich mit 0 auf übliche Weise zurückzukehren.

Michał Mądrzyk
quelle