CodeGym /Kursy /C# SELF /Problemy z precyzją i specjalne wartości

Problemy z precyzją i specjalne wartości

C# SELF
Poziom 6 , Lekcja 3
Dostępny

1. Wprowadzenie

Wydaje się, że skoro komputer jest "mądry", to 0.1 + 0.2 powinno po prostu dać 0.3. Ale to nie do końca tak działa. Zobaczmy to na prostym przykładzie.

double x = 0.1;
double y = 0.2;
double sum = x + y;
Console.WriteLine(sum); // Co wypisze program?
Dodawanie liczb zmiennoprzecinkowych: spodziewamy się 0.3, a co naprawdę?

A teraz spróbuj porównać z 0.3:

Console.WriteLine(sum == 0.3); // A tutaj będzie true czy false?

Jeśli zobaczysz False, nie zdziw się!

Powód tkwi w sposobie reprezentacji liczb w pamięci

Komputery pracują na liczbach zapisanych binarnie. Ale nie każdą dziesiętną liczbę ułamkową da się zapisać jako skończony ułamek binarny, tak samo jak 1/3 nie da się dokładnie zapisać dziesiętnie (0.333...). Na przykład 0.1 w systemie binarnym to nieskończony ułamek i trzeba go "zaokrąglić", żeby go zapisać.

Mówiąc po ludzku, double czasem "udaje", że przechowuje twoją liczbę dokładnie, ale w rzeczywistości trzyma tylko bardzo bliską wartość przybliżoną.

2. Jakie "dziwactwa" mogą się pojawić przy arytmetyce na double?

Zobaczmy praktyczne przykłady.

Przykład 1. Klasyczna "magia" 0.1 + 0.2

double a = 0.1;
double b = 0.2;
double sum = a + b;
Console.WriteLine(sum);            // 0.30000000000000004
Console.WriteLine(sum == 0.3);     // False

Komputer wypisał nie 0.3, tylko 0.30000000000000004. Różnica jest mała, ale jeśli zajmujesz się np. finansami — to już robi się poważne.

Przykład 2. Iteracyjne dodawanie

double result = 0;
for (int i = 0; i < 10; i++)
{
    result += 0.1;
}
Console.WriteLine(result); // 0.9999999999999999

Chciałeś 1.0 — wyszło trochę mniej. Znowu, powód to zaokrąglenia wewnątrz double.

Dlaczego to ważne w prawdziwych zadaniach

Wielu myśli: "Jaka różnica, mały błąd, co z tego!" Zobaczmy przykład ze świata płatności.

Załóżmy, że twój internetowy bank sumuje 100 transakcji po 0.1 euro. Jeśli twój program "gubi" jedną stutysięczną euro na każdej iteracji, to w skali banku już "zgubisz" prawdziwe pieniądze. I wtedy od razu przyjdzie księgowy i zapyta: "Gdzie są nasze pieniądze?!"

3. Jak poprawnie porównywać liczby zmiennoprzecinkowe

Ponieważ double często nie przechowuje dokładnie tej wartości, której oczekujesz, bezpośrednie porównanie przez == może cię "wykiwać". Zamiast tego porównuje się moduł różnicy liczb z jakąś bardzo małą liczbą (epsilon).

Przykład porównania z tolerancją

double a = 0.1 + 0.2;
double b = 0.3;
double epsilon = 0.000001;

if (Math.Abs(a - b) < epsilon)
{
    Console.WriteLine("Prawie równe!"); // Tak porównywać jest bezpieczniej
}

Tutaj mówimy: "Jeśli różnica między liczbami jest mniejsza niż jedna milionowa, uznajemy je za równe".

4. Specjalne wartości double: Infinity, NaN, -Infinity

Typ double przechowuje nie tylko liczby, ale też specjalne wartości. Pojawiają się one w sytuacjach, których matematyk zwykłemu studentowi kategorycznie zabrania.

Nieskończoność (Infinity)

Co się stanie, jeśli podzielisz 1 przez 0?

double result = 1.0 / 0.0;
Console.WriteLine(result); // Infinity

W C# (i wielu innych językach) dzielenie przez 0 dla double nie rzuca wyjątku! Zamiast tego wynik to specjalna wartość "dodatnia nieskończoność".

Minus nieskończoność (-Infinity)

Jeśli podzielisz liczbę ujemną przez 0, dostaniesz minus nieskończoność:

double result = -1.0 / 0.0;
Console.WriteLine(result); // -Infinity

"Nie liczba" (NaN — Not a Number)

Jeśli zrobisz coś naprawdę dziwnego, np. spróbujesz policzyć pierwiastek z liczby ujemnej:

double result = Math.Sqrt(-1);
Console.WriteLine(result); // NaN

Albo wynik dzielenia 0.0 / 0.0:

double result = 0.0 / 0.0;
Console.WriteLine(result); // NaN

NaN — to "wszystko, co nie byłoby liczbą w prawdziwym życiu".

Sprawdzanie specjalnych wartości

W C# są funkcje do sprawdzania specjalnych wartości:

Console.WriteLine(double.IsInfinity(result));    // true jeśli nieskończoność
Console.WriteLine(double.IsNaN(result));         // true jeśli NaN

Tabela: Jak double reaguje na nietypowe operacje

Operacja Wynik Co jest w double
1.0 / 0.0
Infinity +∞
-1.0 / 0.0
-Infinity -∞
0.0 / 0.0
NaN Nie liczba
Math.Sqrt(-1)
NaN Nie liczba
2
Zadanie
C# SELF, poziom 6, lekcja 3
Niedostępne
Różnica w precyzji arytmetyki
Różnica w precyzji arytmetyki
2
Zadanie
C# SELF, poziom 6, lekcja 3
Niedostępne
Porównanie z użyciem epsilon
Porównanie z użyciem epsilon
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION