Dane typu całkowitego (int
, integer
)
Typ danych dostępny praktycznie w każdym języku programowania. Współcześnie, podstawowa dana typu int
zapisywana jest na 32 bitach (4 bajty) w kodzie uzupełnieniowym do dwóch. Z trzydziestu dwu bitów jeden (najbardziej znaczący1) wykorzystywany jest do zapamiętania znaku (zawsze 1 oznacza liczbę ujemną, a 0 — dodatnią), pozostałe służą do zapisu liczby. Dowolną liczbę całkowitą $l$ można przedstawić w postaci rozwinięcia dwójkowego:
$$l = s \sum_{i=0}^{n} e_i 2^i \qquad (e_n \ne 0 \; \text{dla}\; l \ne 0)$$
gdzie $s$ jest znakiem liczby ($s=+1$ lub $s=-1$), a $e_i=0$ lub 1 są cyframi rozwinięcia dwójkowego.
Ogólnie, gdy do zapisu liczby wykorzystujemy $d+1$ bitów, to gdy $n < d$ liczba $l$ może być reprezentowana w wybranej arytmetyce. Może być zapisana jako:
W ten sposób mogą być reprezentowane liczby z zakresu $[-2^d,2^d-1]$. We współczesnych maszynach cyfrowych $d$ przyjmuje wartości: 7, 15, 31, 632. Zatem liczby całkowite w zależności od $d$ mogą przyjmować wartości z różnych zakresów ().
$d$ | typ (C) | zakres |
---|---|---|
7 | char | $[-128,127]$ |
15 | short int | $[-32768,32767]$ |
31 | int | $[-2\,147\,483\,648, 2\,147\,483\,647]$ |
63 | long int | $[-9\,223\,372\,036\,854\,775\,808, 9\,223\,372\,036\,854\,775\,807]$ |
Plik nagłówkowy limits.h
zawiera definicje kilku stałych, które pozwalają zawsze sprawdzić, jakie graniczne wartości mogą przyjmować zmienne określonego typu. Definiują one stałe nazywajace się: SCHAR_MIN
i SCHAR_MAX
(minimalna i maksymalna wartość zmiennej signed char
) SHRT_MIN
i SHRT_MAX
, INT_MIN
i INT_MAX
, LONG_MIN
i LONG_MAX
czy LLONG_MIN
i LLONG_MAX
. Trochę kłopotów może sprawiać typ long int
który może mieć różne zakresy w zależności od tego czy komputer jest 32-bitowy czy 64-bitowy. Podczas programowania, korzystając ze stałych takich typów, nalezy pamiętać o odpowiednich przyrostkach: L
— long, LL
— long long czy U
— unsigned, UL
— unsigned long, itd.
W zasadzie wszystkie obliczenia na liczbach całkowitych dokonywane są dokładnie. Wyjątki od tej reguły są dwa:
Operacja dzielenia nigdy nie wyprowadza poza typ całkowity. Jest to dzielenie całkowitoliczbowe („z resztą").
Wynik działania wykracza poza dopuszczalny zakres; podawany jest wówczas modulo $2^d$.
Dane typu niecałkowitego
Dowolną liczbę rzeczywistą $x\ne 0$ można przedstawić w postaci $$x=s\cdot 2^c m$$ gdzie $s$ ($+1$ lub $-1$) to znak liczby, $c$ — liczba całkowita zwana cechą, a $m$ to liczba rzeczywista z przedziału $[\dfrac{1}{2},1)$ nazywana mantysą. Gdy $x\ne0$ przedstawienie jest jednoznaczne3.
![]“Problemy z liczbami zmiennoprzecinkowymi”(./floating-point.png “Problemy z liczbami zmiennoprzecinkowymi, za Examples of floating point problems)
W realizacji komputerowej cecha liczby $c$ zapisywana jest na $d-t$ bitach, pozostałe $t$ bitów przeznaczonych jest na reprezentację mantysy $m$. Zatem zamiast (na ogół nieskonczonego) rozwinięcia mantysy:
$$m = \sum_{i=1}^\infty e_{-i}\cdot 2^{-i} \qquad (e_{-1}=1;\quad e_i=0\quad \text{lub}\quad 1\quad \text{dla}\quad i>1)$$
korzystamy (gdy mantysa została prawidłowo zaokrąglona do $t$ cyfr): $$m_t = \displaystyle\sum_{i=1}^t e_{-i}\cdot 2^{-i}$$ Wówczas $$|m-m_t| \le \dfrac{1}{2}\cdot 2^{-t}$$
Liczba $x$ binarnie zapisywana jest jako:
Jezeli reprezentację zmiennoprzecinkową liczby $x$ oznaczać będziemy $\operatorname{rd}(x)$ i $\operatorname{rd}(x)= s\cdot 2^cm_t$. Dla $x\ne 0$
$$\left|\dfrac{\operatorname{rd}(x)-x}{x}\right| \le 2^{-t}$$
co można zapisać: $$\operatorname{rd}(x)=x(1+\varepsilon), \qquad \text{gdzie}\quad |\varepsilon| \le 2^{-t}$$
Liczby rzeczywiste reprezentowane są, na ogół, niedokładnie. Błąd względny $\varepsilon$ jest nie większy od $2^{-t}$. We współczesnych komputerach $t$ przyjmuje wartości: 24 dla liczb typu float
(32-bitowych) lub 53 (double
; 64 bity).
Liczba cyfr mantysy decyduje o dokładności liczb rzeczywistych, a liczba cyfr cechy — o ich zakresie. Cecha $c \in [c_\mathrm{min}, c_\mathrm{max}]$, gdzie $c_\mathrm{min} = -c_\mathrm{max}-1=2^{d-t-1}$.
Szczegóły zapisu liczb zmiennoprzecinkowych zawiera norma IEEE-754 .
Można zaryzykować twierdzenie że zakres liczb i ich precyzja są wystarczająco duże aby prowadzić obliczenia w miarę dokładnie, ale jak przyjrzeć się szczegółom — różnie to bywa. A wynik długotrwałych i skomplikowanych działań może być trudny do przewidzenia.
Programiści języka C mogą korzystać ze stałych zdefiniowanych w pliku nagłówkowym float.h
: FLT_MIN
i FLT_MAX
, DBL_MIN
i DBL_MAX
, a nawet LDBL_MIN
/LDBL_MAX
dla liczb poczwórnej dokładności(long double
) . W przypadku liczb typu float
maksymalna wartość to: 3.40282e+38.
Rozważmy prosty przykład:
Niech $z$ będzie liczbą zespoloną $z=a+b\mathrm{i}$, $\mathrm{i}=\sqrt{-1}$. Z definicji, moduł liczby $z$ równa się:
$$|z|=\sqrt{a\cdot a + b\cdot b}$$
W przypadu gdy wartości $a$ i $b$ są bardzo duże (lub bardzo małe) $a^2$ albo $b^2$ mogą nie zmieścić się w dopuszczalnym zakresie. Gdy używamy typu float
, a wartość $a$ jest większa od 1.84467341e19 — będziemy mieli kłopot. Ilustruje niższy program.
#include <stdio.h>
#include <math.h>
float
modul1 (float a, float b)
{
return sqrtf (a * a + b * b);
}
float
modul3 (float a, float b)
{
return fabs (a) * sqrtf (1 + powf (fabs (b) / fabs (a), 2));
}
float
modul2 (float a, float b)
{
if (fabs (a) > fabs (b))
return modul3 (a, b);
else
return modul3 (b, a);
}
int
main (int argc, char *argv[])
{
float m;
m = modul1 (2e-25f, 2e-25f);
printf ("Bardzo male\n");
printf ("modul1=%g\n", m);
m = modul2 (2e-25f, 2e-25f);
printf ("modul2=%g\n", m);
printf ("Bardzo duze\n");
m = modul1 (2e25f, 2e25f);
printf ("modul1=%g\n", m);
m = modul2 (2e25f, 2e25f);
printf ("modul2=%g\n", m);
return 0;
}
Modyfikacja polega na tym, że wzór zapisujmy w alternatywnej postaci. Niech $|a|>|b|$. Wówczas:
$$|z|=\sqrt{a^2+b^2}=a\sqrt{1+\left(\frac{b}{a}\right)^2}.$$
Druga metoda (funkcja modul2()
daje poprawne wyniki gdy jej argumenty są bardzo małe jak i bardzo duże.
Domyślny typ
Matlab
Domyślnym typem dla obliczeń numerycznych w Matlabie jest typ double
.
Nie wyklucza to użycia zmiennych innego typu, ale trzeba je osobno deklarować
Więcej na ten temat można znaleźć w odpowiednim tutorialu.
Mathematica
W przypadku Mathematici, która zazwyczaj wszystkie obliczenia wykonuje symbolicznie sprawa jest znacznie bardziej skomplikowana.
Funkcja N[]
pozwala podać żądaną precyzję obliczeń
In[1] := N[1/7]
Out[1] := 0.142857
Jeżeli precyzji nie podamy — obliczenia będą wykonywane z tak zwaną precyzją maszynową. Wówczas obliczenia prowadzone są w arytmetyce 64-bitowej.
Jeżeli precyzję zadamy — wygląda to tak:
In[1]:= N[1/7,50]
Out[1]= 0.14285714285714285714285714285714285714285714285714
To ten pierwszy z lewej. ↩︎
Raz jeszcze przypominam, że zapis liczby jest na $d+1$ bitach; ten dodatkowy bit, to bit znaku. ↩︎
Określenia mantysa i cecha są niepoprawne, ale ciągle jeszcze tradycyjnie używane przy opisie liczb zmiennoprzecinkowych. Po angielsku zamiast mantysa używa się określenia significant; cecha to exponent. Polska Wikipedia pozostaje przy mantysa, choć dodaje też liczba ułamkowa; obok cecha dodaje wykładnik… ↩︎