2. Wbudowane typy danych

2.4. Typy zmiennoprzecinkowe

Typ zmiennoprzecinkowy w języku C++ służy do zapisywania liczb rzeczywistych. Liczby rzeczywiste odgrywają istotną rolę w obliczeniach naukowych, inżynierskich oraz w wielu aplikacjach, w których kluczowym aspektem jest dokładność. W tym przypadku dokładność zapewniana przez liczby całkowite nie jest wystarczająca.  W języku C++ dostępne są trzy główne typy zmiennoprzecinkowe: float, double oraz long double. Każdy z wymienionych typów zmiennych różni się precyzją i zakresem wartości, które może przechowywać.

Poza standardowym sposobem zapisu liczb tzn. 10, 200.46 możemy zapisywać liczby używając notacji naukowej. Notacja naukowa jest sposobem reprezentowania bardzo dużych lub bardzo małych liczb w zwięzły sposób. Jest szczególnie użyteczna w naukach ścisłych i inżynierii, gdzie liczby mogą mieć wiele cyfr znaczących i szeroki zakres wartości. 

Możemy zapisać liczbę  0.000001234 jako  1.234 \cdot 10^{-6} .

W notacji naukowej wartość liczby obliczamy zgodnie ze wzorem:

 L = m \cdot p^{c}

gdzie:

 m - mantysa,

 p - podstawa,

 c - cecha.

Przy pomocy wzoru możliwe jest wyznaczenie wartość liczby zmiennoprzecinkowej zapisanej w dowolnym systemie pozycyjnym, a nie tylko dziesiętnym. W przypadku dziesiętnego systemu  p = 10 .

Liczby wymierne i niewymierne wchodzą w skład zbioru liczb rzeczywistych, a ich reprezentacja za pomocą notacji naukowej (formy wykładniczej) stanowi oficjalną definicję liczb zmiennoprzecinkowych. W tym momencie warto zastanowić się w jaki sposób liczby zmiennoprzecinkowe są przechowywane w pamieci komputera. Oczywiście zapis jest binarny. W łatwy sposób można zapisać część całkowitą, ale w jaki sposób zapisać część dziesiętną? Najlepiej będzie zaprezentować to na przykładzie. 

Mamy liczbę zmiennoprzecinkową 5.125. Część całkowitą możemy zapisać jako 101
Kolejnym krokiem jest przedstawienie w postaci binarnej liczby 0.125. W tym celu należy wykonać działania przedsatwione w tabeli, które polegają na kolejnym mnożeniu liczb przez 2 oraz zapisie części całkowitej w postaci binarnej.


Liczba wejściowa (ułamkowa) Wynik mnożenia przez 2 Wartość całkowita wyniku
0.125 2*0.125 = 0.250 0
0.250 2*0.250 = 0.500 0
0.500 2*0.500 = 1.000 1

Proces mnożenia kończy się gdy część ułamkowa równa się 0. 
Liczbę zmiennoprzecinkową możemy zapisać jako 101.001.

Należy zwrócić uwagę, że istnieją przypadki w których proces mnożenia może nigdy się nie kończyć. Mamy wtedy do czynienia z liczbami nieskończonymi. W przypadku liczby nieskończonej otrzymujemy pewną powtarzającą się sekwencję binarnych wartości. Mówimy wtedy, że wartość jest w okresie. Możemy to zaobserwować na przykładzie liczby 0.33.

Przedstawienie w postaci binarnej liczby 0.33 wymaga wykonać działania przedsatwione w tabeli, które polegają na kolejnym mnożeniu liczb przez 2 oraz zapisie części całkowitej w postaci binarnej.

Liczba wejściowa (ułamkowa) Wynik mnożenia przez 2 Wartość całkowita wyniku
0.33 2*0.33 = 0.66 0
0.66 2*0.66 = 1.32 1
0.32 2*0.32 = 0.64 0
0.64 2*0.64 = 1.28 1
0.28 2*0.28 = 0.56 0
0.56 2*0.56 = 1.12 1
0.12 2*0.12 = 0.24 0
0.24 2*0.24 = 0.48 0
0.48 2*0.48 = 0.96 0
0.96 2*0.96 = 1.92 1
0.92 2*0.92 = 1.84 1

Zbierając wszystkie części całkowite uzyskane w kolejnych krokach, otrzymujemy: 0.33≈0.0101011100001111…

Reprezentacja binarna liczby  0.33 jest nieskończona i okresowa. W praktyce, można ją przybliżyć do określonej liczby miejsc po przecinku, ale zawsze będzie to tylko przybliżenie. Na przykład:

0.33≈0.0101012 (przybliżenie do 6 miejsc po przecinku)

W celu ujednolicenia sposobu zapisu liczb zmiennoprzecinkowych został wprowadzony standard IEEE754. Standard IEEE 754 to zestaw specyfikacji definiujących sposób reprezentacji oraz operacji na liczbach zmiennoprzecinkowych w komputerach. Opracowany przez Institute of Electrical and Electronics Engineers (IEEE), standard ten jest powszechnie używany we współczesnych systemach komputerowych i językach programowania, zapewniając jednolitą metodę manipulacji liczbami zmiennoprzecinkowymi, co umożliwia przenośność kodu między różnymi platformami.

Liczby zmiennoprzecinkowe w standardzie IEEE 754 są reprezentowane w postaci znormalizowanej za pomocą trzech głównych części:

  1. Znak (1 bit): Określa, czy liczba jest dodatnia (0) czy ujemna (1).
  2. Cecha (eksponenta): Przechowuje przesuniętą wartość wykładnika. Wartość ta jest zapisywana w postaci przesuniętej o wartość zwaną biasem.
  3. Mantysa (część ułamkowa): Przechowuje znaczące cyfry liczby.

W języku C++ możemy więc wyróżnić następujące typy liczb zmiennoprzecinkowych:

  • Typ float to pojedyncza precyzja zmiennoprzecinkowa. Zwykle jest przechowywana na 32 bitach (4 bajty). Zakres liczb wynosi od 1.2E-38 do 3.4E+38, natomiast precyzja jest do 7 cyfr znaczących.
Przykład definicji zmiennej typu float:

float zmienna_rzeczywista;
  • Typ double to podwójna precyzja zmiennoprzecinkowa. Zwykle jest przechowywana na 64 bitach (8 bajtów). Zakres liczb wynosi od 2.2E-308 do 1.8E+308, natomiast precyzja jest do 15 cyfr znaczących.
Przykład definicji zmiennej typu double:

double zmienna_rzeczywista;
  • Typ long double może oferować jeszcze większą precyzję. Jego rozmiar zależy od implementacji, ale zazwyczaj jest przechowywany na co najmniej 80 bitach (10 bajtów) lub więcej. 
Przykład definicji zmiennej typu long double:

long double zmienna_rzeczywista;