Typy danych

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:

Reprezentacja binarna liczby całkowitej

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
7char$[-128,127]$
15short int$[-32768,32767]$
31int$[-2\,147\,483\,648, 2\,147\,483\,647]$
63long 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_MINSCHAR_MAX (minimalna i maksymalna wartość zmiennej signed char) SHRT_MINSHRT_MAX, INT_MIN i INT_MAX, LONG_MINLONG_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:

  1. Operacja dzielenia nigdy nie wyprowadza poza typ całkowity. Jest to dzielenie całkowitoliczbowe („z resztą").

  2. 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.

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:

Reprezentacja binarna liczby zmiennoprzecinkowej

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

  1. To ten pierwszy z lewej. ↩︎

  2. Raz jeszcze przypominam, że zapis liczby jest na $d+1$ bitach; ten dodatkowy bit, to bit znaku↩︎

  3. 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… ↩︎

Poprzedni
Następny