Coś prostego – udowadniamy niedokładność liczb zmiennoprzecinkowych

Zacznijmy od tego, czym są liczby zmiennoprzecinkowe. W programowaniu, jeżeli chcemy użyć liczb dziesiętnych, nie możemy zastosować typu int. Kod:

int a = 3; int b = 2;
return a/b;

nie zwróci nam jak oczekujemy wartości 1,5 tylko jeden. Nie działa tu zaokrąglanie, wynik 1.999999999999999999… zostanie zapisany w pamięci jako 1. Programiści by uniknąć tego typu trudności stosują inne zmienne: float (single precision – typ pojedynczej precyzji, dokładność do 7 cyfr po przecinku), double (double precision – typ podwójnej precyzji, dokładność do 15 cyfr po przecinku) oraz, tylko w niektórych językach, long double (extended/quadraple precision, do 19 cyfr po przecinku).
Przykładowa deklaracja tego typu (C++) liczb to:

float a = 0.1; //w C# deklarujemy floaty dodając po wartości literkę f np. 0.1f; 
double b = 0.1;
long double c = 0.1;

Jak widać do każdej z tych zmiennych przypisaliśmy wartość 0.1. Typy te obsługują wartości dziesiętne, lecz mają ograniczoną dokładność. Między innymi dlatego zostały nazwane liczbami zmiennoprzecinkowymi.

Kontynuując, wrócę do tematu. W języku Python 2.7 napisałem krótki program, w zasadzie pętlę. Pytanie brzmi: Ile razy wykona się ta pętla?

x = 0.0
while x != 1.0:
  print x,
  x += 0.1

Odpowiedź brzmi: nieskończenie wiele razy. Ale dlaczego? Biorąc to na logikę pętla winna zakończyć się po dziesięciu iteracjach, a jako output powinno być widoczne mniej więcej coś takiego.

0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9

Lecz wynik wygląda tak (screena zrobiłem niemal w tym samym momencie co kliknąłem enter):
python petla
I pętla wykonywała się wciąż i wciąż. Sprawdźmy jak zadziała to w C#:

float x = 0.0f;
while(x != 1.0f)
{
  Console.Write(x + " ");
  x += 0.1f;
  Thread.Sleep(100); // dla łatwiejszego screenshota
}

Zrzut ekranu 2016-03-23 20.59.27

Niemal to samo! Ale zauważamy pewne odchylenie. Zamiast 0.8 otrzymaliśmy 0.800001. Dlaczego? Sprawdźmy to (najpierw w Pythonie). Napiszmy skrypt, który wypisze nam wartości do 50 cyfr po przecinku:

x = 0.0
y = 0  #potrzebujemy tylko 10 iteracji
while y != 11:
  print x, "%.50f" % x
  x += 0.1
  y += 1

Otrzymamy taki wynik:

Zrzut ekranu 2016-03-23 21.06.14

Jak widać 0.1 wcale nie jest równe 0.1, 0.2 wcale nie jest równe 0.2 i tak dalej. Analogicznie wygląda to w innych językach programowania (C#, C++ i innych).

Tak więc nie starajmy się porównywać typów float, double etc. Ze względu na swą niedokładność zwrócą nieoczekiwaną przez nas wartość.

 

Reklamy

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Wyloguj / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Wyloguj / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Wyloguj / Zmień )

Connecting to %s