Właściwy sposób porównywania System.Double z „0” (liczba, int?)

87

Przepraszam, to może być łatwe, głupie pytanie, ale muszę wiedzieć, aby mieć pewność.

Mam ten ifwyraz twarzy

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

Czy to wyrażenie jest równe

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

? Bo wtedy mógłbym mieć problem wpisując ifnp z wartością 0,9.

radbyx
źródło
3
// Comparison of floating point numbers with equality // operator. Czy naprawdę musiałeś to określić? :)
George Johnston,
1
Na pewno nie. Istnieje cholernie dużo wartości od 0 do 1. Dlaczego nie po prostu przetestować i przekonać się samemu?
Igby Largeman,
12
Po prostu napisałem to samo co Resharper, aby pokazać, na czym się skupiam.
radbyx
@Charles: Poza tym jest wiele liczb, które są mniejsze niż 0.
Brian,
możliwy duplikat Porównanie podwójnych wartości w C #
Dzyann

Odpowiedzi:

115

Jak blisko chcesz, aby wartość była równa 0? Jeśli przejdziesz przez wiele operacji zmiennoprzecinkowych, które przy „nieskończonej precyzji” mogą dać 0, możesz otrzymać wynik „bardzo blisko” 0.

Zazwyczaj w tej sytuacji chcesz podać jakiś rodzaj epsilon i sprawdzić, czy wynik zawiera się właśnie w tym epsilonie:

if (Math.Abs(something) < 0.001)

Epsilon, którego powinieneś użyć, jest specyficzny dla aplikacji - zależy to od tego, co robisz.

Oczywiście, jeśli wynik powinien wynosić dokładnie zero, proste sprawdzenie równości jest w porządku.

Jon Skeet
źródło
Właściwie potrzebuję, żeby było dokładnie zero, jak by to wyglądało? Szukałem Double.Zero, ale wiem, szczęście. Istnieje stała prawda? Przy okazji, dzięki, dostaję teraz część epsilon :)
radbyx
24
@radbyx: Po prostu użyj == 0. Masz tam dosłownie - to całkiem stałe :)
Jon Skeet
35

Jeśli somethingzostał przypisany z wyniku innej operacji niż something = 0to lepiej użyj:

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

Edycja : ten kod jest nieprawidłowy. Epsilon to najmniejsza liczba, ale nie całkiem zero. Jeśli chcesz porównać liczbę z inną liczbą, musisz pomyśleć o dopuszczalnej tolerancji. Powiedzmy, że nie obchodzi Cię coś poza 0,00001. To jest liczba, której użyjesz. Wartość zależy od domeny. Jednak z pewnością nigdy nie jest to Double.Epsilon.

sonatique
źródło
2
To nie rozwiąże problemu zaokrąglania, na przykład Math.Abs(0.1f - 0.1d) < double.Epsilonjestfalse
Thomas Lule
6
Double.Epsilon jest za mały na takie porównanie. Double.Epsilon to najmniejsza liczba dodatnia, jaką może reprezentować double.
Evgeni Nabokov
3
To nie ma sensu, ponieważ jest to praktycznie to samo, co porównanie z 0. -1
Gaspa79
1
Celem jest porównanie z koncepcją 0 bez użycia ==. Przynajmniej ma sens matematycznie. Zakładam, że masz pod ręką dublera i chcesz porównać to z pojęciem zera bez ==. Jeśli podwójna wartość różni się od 0d z jakiegokolwiek powodu, w tym zaokrąglenia, wypełnienie testowe zwróci fałsz. To porównanie wydaje się poprawne dla każdego podwójnego i zwróci prawdę tylko wtedy, gdy ten podwójny jest najmniejszy niż najmniejsza liczba, którą można przedstawić, co wydaje się dobrą definicją do testowania pojęcia 0, nie?
sonatique
3
@MaurGi: mylisz się: double d = Math.Sqrt(10100)*2; double a = Math.Sqrt(40400); if(Math.Abs(a - d) < double.Epsilon) { Console.WriteLine("true"); }
sonatique
26

Twój somethingjest a doublei poprawnie zidentyfikowałeś to w linii

if (something == 0)

mamy doublepo lewej stronie (lewa oś) i intpo prawej stronie (prawa oś).

Ale teraz wygląda na to, że myślisz, że lewa oś zostanie zamieniona na an int, a następnie ==znak będzie porównywał dwie liczby całkowite. Tak się nie dzieje. Konwersja z double na int jest jawna i nie może nastąpić „automatycznie”.

Zamiast tego dzieje się odwrotnie. Rs jest zamieniany na double, a następnie ==znak staje się testem równości między dwoma podwójnymi. Ta konwersja jest niejawna (automatyczna).

Niektórzy uważają, że lepiej jest pisać

if (something == 0.0)

lub

if (something == 0d)

ponieważ wtedy od razu porównujesz dwa podwójne. Jednak to tylko kwestia stylu i czytelności, ponieważ kompilator w każdym przypadku zrobi to samo.

W niektórych przypadkach istotne jest również wprowadzenie „tolerancji”, jak w odpowiedzi Jona Skeeta, ale ta tolerancja też byłaby double. Oczywiście mogłoby tak być 1.0, gdybyś chciał, ale nie musi to być [najmniej ściśle dodatnia] liczba całkowita.

Jeppe Stig Nielsen
źródło
17

Jeśli chcesz po prostu wyłączyć ostrzeżenie, zrób to:

if (something.Equals(0.0))

Oczywiście jest to prawidłowe rozwiązanie tylko wtedy, gdy wiesz, że dryf nie jest problemem. Często robię to, aby sprawdzić, czy mam zamiar podzielić przez zero.

Russell Phillips
źródło
4

Nie sądzę, żeby było równe, szczerze. Rozważ własny przykład: coś = 0,9 lub 0,0004. W pierwszym przypadku będzie FALSE, w drugim przypadku będzie TRUE. Mając do czynienia z tego typu typami, zwykle definiuję dla siebie dokładność procentową i porównuję z tą precyzją. Zależy od Twoich potrzeb. coś jak...

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


//do something
}

Mam nadzieję że to pomoże.

Tigran
źródło
2
coś musi być dokładnie zerem.
radbyx
więc w tym przypadku masz szczęście :)
Tigran,
3

Oto przykład przedstawiający problem (przygotowany w LinQPadzie - jeśli go nie masz, użyj Console.Writelinezamiast Dumpmetody):

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

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

Oba x i y są teoretycznie takie same i równe: 0,00001, ale z powodu braku „nieskończonej precyzji” te wartości są nieco inne. Niestety na tyle nieznacznie, by wrócić falseprzy porównaniu do 0 w zwykły sposób.

michal-mad
źródło