Jak najlepiej wykorzystać liczby z ułamkami dziesiętnymi

W języku Python, jednym z najczęściej stosowanych rodzajów danych są liczby zmiennoprzecinkowe, reprezentowane przez typ `float`. Są to wartości, które mogą być dodatnie lub ujemne, charakteryzujące się obecnością części dziesiętnej. Do tej kategorii zaliczają się również liczby zapisane w notacji naukowej, gdzie symbol `e` lub `E` oznacza potęgę liczby 10.

Typ danych `float` odgrywa fundamentalną rolę w Pythonie, gdyż pozwala na obsługę szerokiego spektrum wartości rzeczywistych, od bardzo małych po niezwykle duże.

Przykłady użycia liczb zmiennoprzecinkowych w Pythonie prezentują się następująco:

# Przykłady liczb zmiennoprzecinkowych
a = 20.0
b = -51.51345
c = 65e7
d = -1.08E12
e = 2E10

print(type(a))
print(type(b))
print(type(c))
print(type(d))
print(type(e))

Wynik działania powyższego kodu:

<class 'float'>
<class 'float'>
<class 'float'>
<class 'float'>
<class 'float'>

W porównaniu z typami danych, takimi jak liczby całkowite, które pomijają część ułamkową, liczby zmiennoprzecinkowe umożliwiają przeprowadzanie bardziej precyzyjnych obliczeń. Na przykład, w przypadku liczb całkowitych wartość 3,142 byłaby reprezentowana jako 3, podczas gdy typ `float` zachowuje pełną dokładność, przechowując wartość jako 3,142. Z tego powodu, liczby zmiennoprzecinkowe są preferowanym wyborem w obliczeniach matematycznych, gdzie istotna jest dokładność wyników.

Dzięki swoim właściwościom, wartości zmiennoprzecinkowe znalazły szerokie zastosowanie w modelowaniu zjawisk rzeczywistych, uczeniu maszynowym, analizie danych, finansach, ekonomii, grafice komputerowej, wizualizacjach oraz w obliczeniach naukowych i inżynierskich.

Liczby całkowite kontra liczby zmiennoprzecinkowe w Pythonie

Kolejnym popularnym typem danych w Pythonie są liczby całkowite, które w przeciwieństwie do liczb zmiennoprzecinkowych nie posiadają części dziesiętnej. Liczby całkowite obejmują liczby dodatnie, ujemne oraz zero, które nie mają żadnej części ułamkowej.

Liczby całkowite są użyteczne w operacjach, gdzie operujemy na pełnych wartościach, na przykład przy liczeniu lub indeksowaniu. W Pythonie liczby całkowite oznaczane są jako typ `int`.

Oto kilka przykładów liczb całkowitych:

a = 0
b = 968
c = -14

print(type(a))
print(type(b))
print(type(c))

Wyjście:

<class 'int'>
<class 'int'>
<class 'int'>

Poniżej przedstawiono kluczowe różnice pomiędzy liczbami całkowitymi (`int`) i zmiennoprzecinkowymi (`float`) w Pythonie:

Charakterystyka Liczby całkowite (int) Liczby zmiennoprzecinkowe (float)
Reprezentacja Liczby całkowite, ich ujemne odpowiedniki i zero, bez miejsc po przecinku. Liczby rzeczywiste z kropką dziesiętną.
Precyzja Nieograniczona precyzja, wielkość wartości jest ograniczona jedynie dostępną pamięcią. Ograniczona precyzja. Maksymalna wartość to około 1.8 x 10308.
Użycie pamięci Zajmują mniej pamięci. Zajmują więcej pamięci.
Operacje bitowe Często wykorzystywane. Rzadko wykorzystywane.
Zastosowanie Zliczanie, indeksowanie, operacje bitowe. Pomiary, obliczenia naukowe, większość operacji matematycznych.

Różne metody tworzenia i wykorzystania liczb zmiennoprzecinkowych w Pythonie

Prostym sposobem na rozpoczęcie pracy z liczbami zmiennoprzecinkowymi jest przypisanie zmiennej konkretnej wartości `float`:

# Przypisanie zmiennej wartości zmiennoprzecinkowej
a = 3.142

Innym podejściem jest konwersja liczb całkowitych lub ciągów znaków reprezentujących liczby do typu `float` za pomocą konstruktora `float()`. Przekazanie do metody `float()` liczby całkowitej lub ciągu znaków numerycznych spowoduje ich konwersję do wartości zmiennoprzecinkowej. Przykłady:

number1 = 2524
numString1 = "513.523"
numString2 = "1341"
# Konwersja do typu float i zapisanie w zmiennej
a = float(number1)
print(a)
b = float(numString1);
print(b)
c = float(numString2)
print(c)

Wynik:

2524.0
513.523
1341.0

Powyższy kod ilustruje, jak liczby całkowite oraz ciągi znaków numerycznych są przekształcane na wartości `float` za pomocą `float()`. Wynik konwersji jest zapisywany w zmiennych, a następnie wyświetlany.

Kolejnym sposobem na uzyskanie wartości zmiennoprzecinkowych jest wykonywanie działań matematycznych, takich jak dzielenie:

num1 = 20
num2 = 3
result = num1/num2
print("Wynik dzielenia jako liczba całkowita:")
print(int(20/3))
print("Wynik dzielenia jako wartość zmiennoprzecinkowa:")
print(result)
print(type(result))

Wyjście:

Wynik dzielenia jako liczba całkowita:
6
Wynik dzielenia jako wartość zmiennoprzecinkowa:
6.666666666666667
<class 'float'>

Warto zauważyć, że operacja dzielenia zwraca dokładniejszy wynik w formie liczby zmiennoprzecinkowej niż w postaci liczby całkowitej, gdzie część ułamkowa jest pomijana.

Pracując z liczbami zmiennoprzecinkowymi w Pythonie, można napotkać ciekawe rezultaty, wynikające ze sposobu, w jaki są one reprezentowane wewnętrznie w komputerze. Liczby `float` są reprezentowane jako ułamki o podstawie 2 (binarnej).

Większości ułamków dziesiętnych, zwłaszcza tych z nieskończonym rozwinięciem dziesiętnym, nie można precyzyjnie zapisać jako ułamka binarnego. Z tego powodu, liczby zmiennoprzecinkowe są zwykle przechowywane jako przybliżenia rzeczywistych wartości.

Rozważmy liczbę 0.3. Jeżeli przypiszemy wartość 0.3 do zmiennej, nie zostanie ona zapisana jako dokładnie 0.3. Aby to zobaczyć, można skorzystać z funkcji `format()`, która pozwala na wyświetlenie określonej liczby cyfr znaczących. Poniższy kod prezentuje, jak przechowywana jest wartość 0.3 z dokładnością do 20 miejsc po przecinku:

num = 0.3
print("num do 20 miejsc po przecinku")
print(format(num, '.20f'))
print("Wartość zmiennej num")
print(num)

Wynik:

num do 20 miejsc po przecinku
0.29999999999999998890
Wartość zmiennej num
0.3

Jak widać, wartość 0.3, przypisana do zmiennej `num`, nie jest wewnętrznie przechowywana jako dokładnie 0.3. Wyświetlając zmienną `num`, otrzymujemy wartość zaokrągloną.

Ze względu na powyższe, praca z wartościami `float` może prowadzić do nieoczekiwanych wyników. Na przykład, obliczając 0.3 + 0.3 + 0.3, spodziewamy się wyniku 0.9. Python jednak zwróci wartość, która nie będzie dokładnie równa 0.9, ponieważ przechowuje binarne przybliżenia ułamkowe rzeczywistej wartości. Demonstracja:

sum = 0.3 + 0.3 + 0.3
answer = 0.9
print("Czy suma równa się odpowiedzi: ")
print(sum == answer)
print("Wewnętrzna reprezentacja sumy to: ")
print(sum)
print("Odpowiedź z ręcznego obliczenia to: ")
print(answer)

Wyjście:

Czy suma równa się odpowiedzi: 
False
Wewnętrzna reprezentacja sumy to: 
0.8999999999999999
Odpowiedź z ręcznego obliczenia to: 
0.9

Podsumowując, należy pamiętać, że Python nie przechowuje wewnętrznie dokładnych wartości `float`. Zamiast tego, używa przybliżeń, co może prowadzić do nieoczekiwanych rezultatów podczas porównywania wartości.

W celu uniknięcia problemów z dokładnością, warto zaokrąglać wartości zmiennoprzecinkowe do odpowiedniej liczby miejsc po przecinku przed ich porównywaniem. Dla bardziej precyzyjnych operacji na liczbach zmiennoprzecinkowych, zalecane jest skorzystanie z wbudowanego modułu `decimal`.

Moduł dziesiętny w Pythonie

W sytuacjach wymagających wysokiej dokładności, na przykład w obliczeniach finansowych i naukowych, użycie typu `float` może być niewystarczające. W celu zapewnienia wysokiej dokładności, Python oferuje wbudowany moduł `decimal`, który umożliwia pracę z liczbami zmiennoprzecinkowymi o zwiększonej precyzji.

W przeciwieństwie do typu `float`, który przechowuje liczby jako binarne reprezentacje zmiennoprzecinkowe zależne od architektury komputera, moduł `decimal` wykorzystuje dziesiętną reprezentację, niezależną od maszyny, co zapewnia większą precyzję.

Moduł `decimal` pozwala na dokładne przedstawienie i wykorzystanie liczb dziesiętnych w obliczeniach, oferując również prawidłowo zaokrągloną arytmetykę zmiennoprzecinkową.

Aby rozpocząć korzystanie z modułu `decimal`, należy go zaimportować:

import decimal

Aby zilustrować korzyści płynące z użycia modułu `decimal`, powtórzmy wcześniejsze porównanie sumy 0.3 + 0.3 + 0.3 z wartością 0.9:

import decimal

sum = decimal.Decimal('0.3') + decimal.Decimal('0.3') + decimal.Decimal('0.3')
answer = decimal.Decimal('0.9')
print("Czy suma równa się odpowiedzi: ")
print(sum == answer)
print("Wewnętrzna reprezentacja sumy to: ")
print(sum)
print("Odpowiedź z ręcznego obliczenia to: ")
print(answer)

Wynik:

Czy suma równa się odpowiedzi: 
True
Wewnętrzna reprezentacja sumy to: 
0.9
Odpowiedź z ręcznego obliczenia to: 
0.9

Podsumowując, gdy wymagana jest wysoka dokładność przy pracy z liczbami zmiennoprzecinkowymi, zawsze należy rozważyć użycie modułu `decimal`.

Typowe błędy podczas pracy z liczbami zmiennoprzecinkowymi

Wiele błędów, które pojawiają się podczas pracy z typem `float` w Pythonie, wynika z niepełnego zrozumienia sposobu, w jaki liczby zmiennoprzecinkowe są wewnętrznie reprezentowane. Na przykład, wartość 0.3 nie będzie przechowywana dokładnie jako 0.3. Zakładanie, że wartości `float` są przechowywane bez przybliżeń może prowadzić do nieoczekiwanych błędów.

Częstym błędem jest błąd zaokrąglenia, który może wystąpić podczas wykonywania obliczeń matematycznych na wartościach `float`. Ze względu na to, że Python nie jest w stanie precyzyjnie reprezentować rzeczywistych wartości zmiennoprzecinkowych, możemy napotkać sytuacje, w których wyniki nie będą zgodne z naszymi oczekiwaniami.

Z powodu błędów zaokrąglania, możemy napotkać trudności przy próbie porównania równości pomiędzy wartościami `float`. Należy zachować ostrożność i być świadomym potencjalnych, nieoczekiwanych rezultatów podczas pracy z typem `float` w Pythonie.

Aby uniknąć błędów, które mogą wystąpić podczas pracy z liczbami zmiennoprzecinkowymi, zaleca się stosowanie wbudowanego modułu `decimal`. Zapewni to bardziej przewidywalne i dokładne wyniki obliczeń.

Podsumowanie

Jako programista używający Pythona, z pewnością będziesz korzystać z typu danych `float`. Aby unikać błędów z nim związanych, kluczowe jest zrozumienie, w jaki sposób Python wewnętrznie reprezentuje liczby zmiennoprzecinkowe. Ze względu na to, że Python nie jest w stanie przechowywać precyzyjnych wartości liczb `float`, należy unikać porównań równości, gdyż może to prowadzić do błędów.

W przypadku, gdy w aplikacji potrzebne są dokładne wyniki, powinno się unikać bezpośredniego użycia typu `float` i zamiast tego korzystać z wbudowanego modułu `decimal`, który zapewnia dokładne i niezależne od maszyny wyniki obliczeń zmiennoprzecinkowych.

Zachęcamy również do zapoznania się z artykułami dotyczącymi funkcji `Python Itertools` oraz obsługi wyjątków `Python Try except`.