Porównywanie wartości podwójnych w C#

Mam zmienną double o nazwie x. W kodzie {[2] } otrzymuje przypisaną wartość 0.1 i sprawdzam ją w instrukcji " if " porównującej x i 0.1

if (x==0.1)
{
----
}

Niestety nie wpisuje się if

  1. Czy powinienem użyć Double Czy double?

  2. Jaki jest tego powód? Czy możesz zasugerować rozwiązanie tego problemu?

Author: Vadim Ovchinnikov, 2009-09-09

15 answers

Jest to standardowy problem ze względu na sposób, w jaki komputer przechowuje wartości zmiennoprzecinkowe. Wyszukaj tutaj "problem zmiennoprzecinkowy", a znajdziesz mnóstwo informacji.

W skrócie – float/double nie może przechowywać 0.1 dokładnie. Zawsze będzie trochę dziwnie.

Możesz spróbować użyć typu decimal, który przechowuje liczby w notacji dziesiętnej. W ten sposób 0.1 będzie można dokładnie przedstawić.


Chciałeś poznać powód:

Float/double są przechowywane jako binarne ułamki, nie ułamki dziesiętne. Do zilustrowania:

12.34 w zapisie dziesiętnym (czego używamy) oznacza

1 * 101 + 2 * 100 + 3 * 10-1 + 4 * 10-2

Komputer przechowuje liczby zmiennoprzecinkowe w ten sam sposób, z tym, że używa bazy 2: 10.01 oznacza

1 * 21 + 0 * 20 + 0 * 2-1 + 1 * 2-2

Teraz, prawdopodobnie wiesz, że są pewne liczby, które nie mogą być przedstawione w pełni z naszej notacji dziesiętnej. Na przykład, 1/3 w zapisie dziesiętnym jest 0.3333333…. To samo dzieje się w notacji binarnej, z tym, że liczby, które nie mogą być reprezentowane dokładnie są różne. Wśród nich jest liczba 1/10. W notacji binarnej, czyli 0.000110011001100….

Ponieważ notacja binarna nie może jej dokładnie Przechowywać, jest przechowywana w sposób zaokrąglony. Stąd twój problem.

 76
Author: Vilx-,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-15 17:33:12

double i Double są takie same (double jest aliasem dla Double) i mogą być używane zamiennie.

Problem z porównaniem dwójki z inną wartością polega na tym, że dwójki są wartościami przybliżonymi, a nie dokładnymi. Więc kiedy ustawisz x na 0.1 może to być w rzeczywistości zapisane jako 0.100000001 lub coś w tym stylu.

Zamiast sprawdzać równość, należy sprawdzić, czy różnica jest mniejsza niż określona minimalna różnica (tolerancja). Coś w stylu:

if (Math.Abs(x - 0.1) < 0.0000001)
{
    ...
}
 33
Author: Rune Grimstad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-09 14:52:08

Potrzebujesz kombinacji Math.Abs on X-Y i value, aby porównać z.

Możesz użyć następującej metody rozszerzenia

public static class DoubleExtensions
    {
        const double _3 = 0.001;
        const double _4 = 0.0001;
        const double _5 = 0.00001;
        const double _6 = 0.000001;
        const double _7 = 0.0000001;

        public static bool Equals3DigitPrecision(this double left, double right)
        {
            return Math.Abs(left - right) < _3;
        }

        public static bool Equals4DigitPrecision(this double left, double right)
        {
            return Math.Abs(left - right) < _4;
        }

        ...

Ponieważ rzadko wywołujesz metody na double z wyjątkiem ToString uważam, że jest to całkiem bezpieczne rozszerzenie.

Następnie możesz porównać x i y Jak

if(x.Equals4DigitPrecision(y))

 14
Author: Valentin Kuzub,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-28 02:00:24

Porównywanie liczb zmiennoprzecinkowych nie zawsze może być wykonane dokładnie z powodu zaokrąglania. Do porównania

(x == .1)

Komputer naprawdę porównuje

(x - .1) vs 0

Wynik sybktrakcji nie zawsze może być dokładnie określony ze względu na sposób reprezentacji liczb zmiennoprzecinkowych na maszynie. Dlatego otrzymujesz wartość niezerową i warunek jest obliczany na false.

Aby przezwyciężyć to porównanie

Math.Abs(x- .1) vs some very small threshold ( like 1E-9)
 10
Author: sharptooth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-09 10:22:36

Z dokumentacji:

Precyzja w porównaniach Metoda Equals powinna być stosowana ostrożnie, ponieważ dwie pozornie równoważne wartości mogą być nierówne ze względu na różną dokładność tych dwóch wartości. Poniższy przykład informuje, że Podwójna wartość .3333 i podwójne zwrócone przez podzielenie 1 przez 3 są nierówne.

...

Zamiast porównywać dla równości, jedna zalecana technika polega na zdefiniowaniu dopuszczalnego marginesu różnica między dwiema wartościami (np.01% jednej z wartości). Jeżeli wartość bezwzględna różnicy między tymi dwoma wartościami jest mniejsza lub równa temu marginesowi, różnica prawdopodobnie wynika z różnic w dokładności, a zatem wartości mogą być równe. Poniższy przykład wykorzystuje tę technikę do porównania .33333 i 1/3, dwie podwójne wartości, które poprzedni przykład kodu uznał za nierówne.

Więc jeśli naprawdę potrzebujesz sobowtóra, powinieneś użyć techique opisane w dokumentacji. Jeśli możesz, Zmień go na dziesiętny. będzie wolniej , ale nie będziesz miał tego typu problemów.

 4
Author: Alfred Myers,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-09 10:22:13

Podwójne i podwójne są identyczne.

Zobacz http://www.yoda.arachsys.com/csharp/floatingpoint.html . Krótko mówiąc: double nie jest dokładnym typem i minutowa różnica między " x " i " 0.1 " wyrzuci go.

 3
Author: Hans Kesting,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-09 10:18:27

Dokładne porównanie wartości zmiennoprzecinkowych nie zawsze działa ze względu na problem zaokrąglania i reprezentacji wewnętrznej.

Spróbuj nieprecyzyjnego porównania:

if (x >= 0.099 && x <= 0.101)
{
}

Inną alternatywą jest użycie typu danych decimal.

 3
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-09 10:18:28

Użyj decimal. Nie ma tego "problemu".

 2
Author: Svetlozar Angelov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-15 14:23:02

1) powinienem użyć podwójnego czy podwójnego???

Double i double to to samo. double jest tylko słowem kluczowym C# działającym jako alias dla klasy System.Double Najczęściej używa się aliasów! To samo dla string (System.String), int(System.Int32)

Zobacz także Wbudowana tabela typów (C # Reference)

 1
Author: Lars Corneliussen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-09 10:21:00

Double (w niektórych językach nazywany float) jest fraut z problemami z powodu problemów z zaokrągleniem, jest dobry tylko wtedy, gdy potrzebujesz przybliżonych wartości.

Typ danych dziesiętnych robi to, co chcesz.

Dla odniesienia decimal i Decimal są takie same w. Net C#, podobnie jak typy double i Double, oba odnoszą się do tego samego typu (decimal i double są bardzo różne, jak już widziałeś).

Uważaj, że typ danych dziesiętnych ma pewne koszty związane z nim, więc użyj go z uwaga, jeśli patrzysz na pętle itp.

 1
Author: Timothy Walters,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-09 10:21:50

Reprezentacje liczb zmiennoprzecinkowych są notorycznie niedokładne (ze względu na sposób, w jaki pływaki są przechowywane wewnętrznie), np. x może mieć wartość 0.09999999999 lub 0.100000001 i twój warunek się nie powiedzie. Jeśli chcesz określić, czy pływaki są równe, musisz określić, czy są równe w ramach określonej tolerancji.

Tzn.

if(x - 0.1 < tol)
 0
Author: Massif,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-09 10:18:02

Jako ogólna zasada:

Podwójna reprezentacja jest wystarczająco dobra w większości przypadków, ale w niektórych sytuacjach może się źle skończyć. Użyj wartości dziesiętnych, jeśli potrzebujesz pełnej precyzji (jak w aplikacjach finansowych).

Większość problemów z dublami nie wynika z bezpośredniego porównania, jest to wynik kumulacji kilku operacji matematycznych, które wykładniczo zakłócają wartość z powodu błędów zaokrąglania i ułamków (zwłaszcza z mnożeniem i podziałem).

Sprawdź twoja logika, jeśli kod jest:

x = 0.1

if (x == 0.1)

To nie powinno się nie udać, to proste do niepowodzenia, jeśli wartość X jest obliczana bardziej złożonymi środkami lub operacjami, jest całkiem możliwe, że metoda ToString używana przez debugger używa inteligentnego zaokrąglania, może możesz zrobić to samo (jeśli jest to zbyt ryzykowne wróć do używania dziesiętnego): {]}

if (x.ToString() == "0.1")
 0
Author: Jorge Córdoba,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-09 10:30:23

Fajny hack znalazłem jest użycie metody .GetHashCode(), która zwraca int, który reprezentuje podwójne, tzn.

(0.4d + 0.3d + 0.2d + 0.1d).GetHashCode() //returns -1072693248

1d.GetHashCode() //returns 1072693248

Więc jak już zauważyłeś możemy użyć czegoś takiego

public static bool AccurateEquality(double first,double second)
{
    return Math.Abs(first.GetHashCode()) == Math.Abs(second.GetHashCode());
}

Użycie : AccurateEquality((0.4d + 0.3d + 0.2d + 0.1d),1) //returns true

While: (0.4d + 0.3d + 0.2d + 0.1d) == 1d //returns false

Próbowałem go w wielu przypadkach i wydaje się, że działa dobrze.

 0
Author: bigworld12,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-28 04:47:07

Biorąc napiwek z bazy kodu Javy, spróbuj użyć .CompareTo i przetestuj zerowe porównanie. Zakłada to, że funkcja .CompareTo uwzględnia równość zmiennoprzecinkową w dokładny sposób. Na przykład,

System.Math.PI.CompareTo(System.Math.PI) == 0

Ten predykat powinien zwrócić true.

 0
Author: Nicole Tedesco,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-09 20:47:45

Większość powyżej sposobów lub po głupi sposób rozszerzenia!

public static bool EqualsTo(this double value, double value2)
{
    var bytes1 = BitConverter.GetBytes(value);
    var bytes2 = BitConverter.GetBytes(value2);

    var long1 = BitConverter.ToInt64(bytes1, 0);
    var long2 = BitConverter.ToInt64(bytes2, 0);

    return long1 == long2;
}
 -7
Author: Ehsan Mohammadi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-09 09:48:05