Spis treści Skorowidz Poziom główny Poziom nadrzędny ©
«« Początek »» Koniec

Podstawowe typy danych

Ten rozdział jest wspólny dla drugiej i trzeciej części opracowania.

Praktykom chodzi głównie o dane. Nawet najlepsze schematy postępowania potrzebują danych dla wytworzenia z nich wyników. W trakcie przetwarzania dane podlegają przekształceniom. W bieżącym rozdziale zwracamy uwagę na to, jak opisywać dane, i jak od tego opisu zależą możliwości ich przetwarzania. Wiedza ta znajduje zastosowanie podczas użytkowania dowolnych systemów automatycznych, w szczególności arkuszy kalkulacyjnych, systemów baz danych i języków programowania.

Mówiąc dane, mamy na myśli cyfrowo zapisaną informację uzupełnioną o sposób jej interpretowania. Wiedza o sensie i charakterze poszczególnych danych wiąże się z wymaganiem przyjmowania przez nie wartości z określonego zbioru, i z koniecznością wykonywania na nich określonych operacji.

Ogół właściwości danych związanych z przyjmowaniem wartości z określonego zbioru oraz z możliwością wykonywania określonych operacji na ich wartościach, nosi nazwę typu danych. Dokonamy teraz przeglądu najważniejszych typów danych.

Dane logiczne

Klasyczny rachunek zdań posługuje się dwiema wartościami logicznymi: T (Prawda, True) i F (Fałsz, False).

Istnienie tylko dwóch różnych możliwych wartości zachęca do ich reprezentowania za pomocą pojedynczej cyfry dwójkowej (czyli bitu). Z uwagi na właściwości algebraiczne działań logicznych, naturalne jest stosowanie dla wartości T liczby 1 zapisanej za pomocą cyfry dwójkowej 1, zaś dla wartości F — liczby 0 zapisanej za pomocą cyfry dwójkowej 0.

Takie utożsamienie rzeczywiście bywa stosowane, nie jest jednak regułą. Inna konwencja użytkowa, z którą mogą się spotkać np. użytkownicy arkuszy kalkulacyjnych, wiąże wartość F z liczbą 0, lecz dopuszcza także reprezentowanie wartości T przez dowolną liczbę całkowitą różną od zera.

Na wartościach logicznych określone są podstawowe działania rachunku zdań: alternatywa (oznaczana zależnie od języka i systemu symbolami or, | albo ||), koniunkcja (oznaczana symbolami and, & albo &&) i negacja (oznaczana symbolami not albo !).

Operacje logiczne w dwuwartościowym rachunku zdań
koniunkcja
andFT
FFF
TFT
alternatywa
orFT
FFT
TTT
negacja
notFT
TF

W większości języków programowania operacje logiczne są realizowane za pomocą operatorów, czyli podobnie jak działania arytmetyczne w powszechnie stosowanej notacji matematycznej.

W arkuszach kalkulacyjnych operacje logiczne są realizowane za pomocą funkcji, zazwyczaj nazywanych or(), and() oraz not(), albo — w polskojęzycznych interfejsach — lub(), i() oraz nie().

Zauważmy, że tabliczka działania logicznego and (koniunkcji) jest równoważna tabliczce mnożenia liczb, w której jedynymi czynnikami są 0 oraz 1. Niestety, tabliczka działania logicznego or (alternatywy) nie wykazuje analogicznego podobieństwa z dodawaniem liczb całkowitych zero-jedynkowych.

Z uwagi na ten fakt, i na wspomnianą wyżej niejednoznaczność reprezentacji wartości T, radzimy unikać używania operacji arytmetycznych + i * na danych liczbowych (nawet zero-jedynkowych) reprezentujących wartości logiczne, zamiast operacji or i and na odpowiadających im danych typu logicznego.

Z wartościami logicznymi użytkownik ma do czynienia najczęściej przy okazji formułowania bądź sprawdzania warunków. Są to wyrażenia zbudowane zgodnie z regułami rachunku zdań logicznych, których wartość da się wyznaczyć na podstawie dostarczonych danych.

Wyniki operacji przyrównywania (zależnie od systemu oznaczanych symbolami =, ==, === albo .EQ., oraz !=, <> albo .NE.) i różnych wariantów porównywania (< albo .LT., <= albo .LE., >= albo .GE., > albo .GT.), niezależnie od typu argumentów, są zawsze typu logicznego.

W niektórych systemach informacyjnych (np. we współczesnych relacyjnych bazach danych) stosowany jest rachunek zdań oparty nie na dwóch, a na trzech podstawowych wartościach logicznych. Oprócz wartości T i F mamy w nim do czynienia z wartością ? (Nieznana, Unknown). W systemach baz danych jest ona reprezentowana przez specjalną stałą o nazwie NULL.

Operacje logiczne w trójwartościowym rachunku zdań
koniunkcja
andFT?
FFFF
TFT?
?F??
alternatywa
orFT?
FFT?
TTTT
??T?
negacja
notFT?
TF?

Zauważmy, że w trójwartościowym rachunku logicznym utożsamienie wartości logicznej z cyfrą dwójkową jest niemożliwe.

Dane liczbowe

Istotą danych liczbowych jest możliwość wykorzystywania ich w obliczeniach. Przedstawianie liczb w różnych systemach liczbowych (np. dwójkowym, dziesiętnym, szesnastkowym) i notacjach (np. arabskiej, słownej, rzymskiej) wpływa na sposób ich prezentacji, ale nie zmienia ich wartości.

Liczby całkowite

Model cyfrowy

Do cyfrowego przechowywania danych całkowitoliczbowych zazwyczaj używany jest system pozycyjny. We współczesnych systemach jest to najczęściej system dwójkowy, chociaż do prezentacji używa się także systemów: dziesiętnego lub szesnastkowego.

Rysunek: co się da zapisać za pomocą n cyfr

W większości współczesnych systemów liczących do zapisu pojedynczej danej całkowitoliczbowej używa się z góry ustalonej liczby cyfr. Konsekwencją tego założenia jest fakt, że istnieje tylko skończenie wiele różnych liczb całkowitych możliwych do przedstawienia.

Liczba cyfr używanych do zapisania pojedynczej liczby decyduje o zakresie wartości liczbowych możliwych do przechowania.

Na przykład przy użyciu 16 cyfr dwójkowych jesteśmy w stanie przedstawić dowolną liczbę całkowitą z zakresu od 0000000000000000(2) = 0 do 1111111111111111(2) = 65535, albo — jeżeli zgodzimy się traktować część zapisu jako znak liczby — z zakresu od −215 = −32768 do +215−1 = 32767 (asymetria bierze się stąd, że nie ma potrzeby posiadania dwóch notacji: +000000000000000(2) oraz −000000000000000(2) dla liczby 0).

Użycie większej ilości cyfr pozwala odpowiednio rozszerzyć ten zakres, np. liczby 32-bitowe muszą mieścić się w zakresie od 0 do 232−1 = 4294967295 lub od −231 = −2147483648 do +231−1 = 2147483647.

Przedstawienie liczby większej, niż to wynika z ograniczeń przyjętego modelu cyfrowego, jest niewykonalne. Próba taka może się skończyć trojako, zależnie od rozwiązań konstrukcyjnych używanego systemu: albo nastąpi błąd przepełnienia (2147483647 + 1 = Błąd nadmiaru), albo system nie zasygnalizuje błędu, ale zwróci niepoprawny wynik, obcinając cyfrę nie mieszczącą się w zakresie (2147483647 + 1 = -2147483648), albo zwróci wynik poprawny, ale należący do innego typu danych, np. całkowity o innej liczbie cyfr: 2147483647 + 1 = 2147483648L (Python), lub zmiennopozycyjny: 2147483647 + 1 = 2147483648.0 (arkusze kalkulacyjne). W tym ostatnim przypadku możliwa jest utrata dokładności wyniku.

Istnieją również notacje cyfrowe liczb całkowitych dopuszczające dowolną liczbę cyfr. Ograniczeniem staje się wtedy pojemność urządzeń pamięci.

Charakterystyka operacji arytmetycznych

Operacje arytmetyczne: dodawania (+), odejmowania (-), mnożenia (*) dla wszystkich wartości argumentów oraz potęgowania (^, **) dla nieujemnych wykładników dają dokładne wyniki, o ile te mieszczą się w zakresie wartości. Operacja dzielenia (/) — podobnie jak potęgowania (^, **) dla ujemnych wykładników — dają wyniki typu rzeczywistego, zatem na ogół przybliżone. Dostępna w niektórych językach i środowiskach operacja dzielenia całkowitego (oznaczana symbolem //, /, lub div) zwraca dokładne wyniki, będące danymi typu całkowitego.

W związku z tym, działania arytmetyczne na danych całkowitoliczbowych zachowują wszystkie właściwości znane z algebry liczb (przemienność, łączność, rozdzielności) — pod warunkiem, że zakres danych nie zostanie przekroczony.

Kolejność wykonywania działań nie wynika z modelu danych, tylko z reguł interpretacji wyrażeń. W przeważającej większości współczesnych środowisk jest ona zgodna z potoczną notacją matematyczną, wprowadzającą priorytety działań. Kolejność inną, niż to wynika z priorytetów, wymusza się za pomocą nawiasów.

Istnieją środowiska, w których notacja wyrażeń różni się istotnie od opisanej wyżej. Należy do nich np. język programowania LISP. Jednak nawet w popularnych arkuszach kalkulacyjnych spotykamy się z przypadkami pojedynczych odstępstw od tych reguł.

Liczby rzeczywiste

Notacja tekstowa liczb rzeczywistych

Do prezentacji liczb rzeczywistych zazwyczaj stosowany jest system dziesiętny.

W tekstowych przedstawieniach liczb rzeczywistych niezbędny jest symbol separatora części ułamkowej. Rolę tę pełni znak kropki lub przecinka, zależnie od rozwiązań typograficznych przyjętych w danej społeczności narodowej, i od ich uwzględnienia w systemach informatycznych. W środowiskach, w których niezależność notacji od zwyczajów lokalnych jest priorytetem (np. w językach programowania czy też w formatach wymiany danych) na ogół używa się znaku kropki przejętego z notacji anglosaskiej.

Do wewnętrznej notacji liczb rzeczywistych w systemach komputerowych zazwyczaj stosowany jest system dwójkowy. Jest to przyczyną dodatkowych problemów w związku z faktem, że nie wszystkie liczby będące skończonymi ułamkami dziesiętnymi są jednocześnie skończonymi ułamkami dwójkowymi. Taką „kłopotliwą” liczbą jest np. liczba 0.1, której rozwinięcie dwójkowe ma postać (0.0001100110011…)2 = (0.0(0011))2.

Liczby stałopozycyjne

Najprostsza z możliwych cyfrowych reprezentacji liczb rzeczywistych nosi nazwę stałopozycyjnej (stałoprzecinkowej, fixed-point). Dane są reprezentowane tak samo, jak dane całkowite, ale ustalona liczba cyfr jest przeznaczona na opis części ułamkowej.

Rysunek: … 0.00 0.01 0.02 0.03 … 0.99 1.00 1.01 …

Tym sposobem da się wyrazić jedynie takie liczby, których część ułamkowa ma rozwinięcie nie dłuższe liczba cyfr przyjęta do reprezentowania tej części. Liczby stałopozycyjne są więc w rzeczywistości podzbiorem liczb wymiernych.

Operacje arytmetyczne dodawania (+) i odejmowania (-) oraz mnożenie przez liczbę całkowitą albo dają dokładne wyniki, albo powodują błąd przekroczenia zakresu. Dostępna w niektórych językach i środowiskach operacja dzielenia całkowitego (//) daje wyniki dokładne. Inne operacje, w szczególności mnożenie, dzielenie i potęgowanie, dają wyniki przybliżone. Błąd obliczeń wiąże się tu z obcięciem zbyt długiego rozwinięcia części ułamkowej.

Rysunek: 1.01 + 2.93 = 3.94. 1.01 ⋅ 2.93 = (2.9593 zaokrąglona do) 2.96.
Położenie wartości 2.9593 na osi liczb stałopozycyjnych o dwóch cyfrach dziesiętnych

Dodajmy, że wskutek tego działania stałopozycyjne nie są łączne.

Na przykład w obliczeniach stałopozycyjnych z dwiema cyframi dziesiętnymi

300.00 * (100.00 / 3.00) = 300.00 * 33.33 = 9999.00

podczas gdy

(300.00 * 100.00) / 3.00 = 30000.00 / 3.00 = 10000.00

Notacji stałopozycyjnej nie należy używać bez naprawdę istotnego powodu.

Najważniejszym zastosowaniem notacji stałopozycyjnej są rozliczenia finansowe. Notacja ta ma implementacje w systemach bazodanowych (typ decimal i typy numeric ze wskazaną dokładnością) oraz w niektórych językach programowania. W arkuszach kalkulacyjnych emuluje się ją przez wymuszenie odczytu zawartości komórki zgodnie z dokładnością jej prezentacji (opcja „dokładność jak pokazano”).

Liczby zmiennopozycyjne

Cyfrowa reprezentacja liczb rzeczywistych zwana zmiennopozycyjną (zmiennoprzecinkową, floating-point) korzysta z notacji postaci ±m⋅10c w systemie dziesiętnym lub z postaci ±m⋅2c w systemie dwójkowym, gdzie liczby: c zwana wykładnikiem lub cechą, oraz m zwana ułamkiem lub mantysą, są danymi o stałej liczbie cyfr. Pierwszą cyfrę mantysy interpretuje się jako jej część całkowitą.

Ponadto od mantysy m wymaga się, by albo była równa zeru, albo by jej pierwsza cyfra była niezerowa. O danych zmiennopozycyjnych spełniających ten warunek mówi się, że są znormalizowane. Wykładnik natomiast mówi o tym, w którą stronę i o ile cyfr przesunąć znak separatora części dziesiętnej, aby z mantysy otrzymać reprezentowaną liczbę.

Rola mantysy i wykładnika jest uwidoczniona w notacji liczbowej zwanej półlogarytmiczną lub naukową. Występują w niej: znak liczby (+ lub ), liczba dziesiętna (mantysa), symbol E (od słowa exponent, czyli ‘wykładnik’), po czym liczba całkowita będąca wykładnikiem potęgi o podstawie 10. Poniżej zamieszczamy kilka przykładów notacji półlogarytmicznej z mantysą pięciocyfrową.

1.0000E+00	# liczba 1.0
-1.0000E+01	# liczba −10.0
3.3333E-01	# liczba 0.33333 (najlepsze przybliżenie liczby 1/3 za pomocą mantysy 5-cyfrowej)
3.4500E-10	# liczba 0.000000000345
1.2500E+03	# liczba 1250.0, notacja znormalizowana
0.1250E+04	# liczba 1250.0, notacja nieznormalizowana
12.500E+02	# liczba 1250.0, notacja niedozwolona. W mantysie tylko jedna cyfra może wyrażać część całkowitą 

Dane liczbowe zmiennopozycyjne są powszechnie stosowane zarówno w sprzęcie obliczeniowym, jak w oprogramowaniu.

Kolejność wykonywania działań na liczbach zmiennopozycyjnych jest w taka sama, jak w przypadku liczb całkowitych. Podobnie też można nią sterować za pomocą nawiasów. Najważniejsza różnica polega na zasadniczej niedokładności obliczeń zmiennopozycyjnych. Ma ona kilka przyczyn.

Rysunek: rozmieszczenie liczb zmiennopozycyjnych na osi liczbowej

W modelu zmiennopozycyjnym dokładnie odwzorowane są jedynie te liczby, których rozwinięcie składa się z nie większej ilości niezerowych cyfr, niż ilość cyfr przeznaczonych na przechowywanie mantysy. Tak więc, podobnie jak liczby stałopozycyjne, także wszystkie liczby zmiennopozycyjne w rzeczywistości są liczbami wymiernymi.

Mimo, że różnych liczb tej postaci jest z punktu widzenia użytkownika niewyobrażalnie wiele, ich ilość jest skończona. Istnieje więc realna szansa, że dla niektórych liczb, czy to wprowadzonych do systemu przez użytkownika, czy to będących wynikiem operacji przeprowadzonej w pamięci, nie ma możliwości ich dokładnego przedstawienia. Zapis zmiennopozycyjny jest więc z natury obarczony pewnym błędem. Z tych powodów podstawowe operacje arytmetyczne (+, -, *, /, ^) dla argumentów zmiennopozycyjnych z reguły dają wyniki przybliżone. Błąd zależy przy tym od wartości argumentów, a w szczególności od tego, na ile różnią się one rzędem wielkości.

Np. przy zastosowaniu 3-cyfrowej dziesiętnej mantysy mamy 3.33e+00 + 3.33e-01 = 3.66e+00, ponieważ dokładny wynik 3.33 + 0.333 = 3.663 wymaga większej liczby cyfr. Dlaczego tak jest, lepiej widać w notacji słupkowej znanej z pisemnego wykonywania działań:

    3.33	czyli 3.33e+00 
+   0.333	czyli 3.33e-01 (przedstawienie 0.333 jest nieznormalizowane)
---------- 
=   3.663 	ta liczba wymaga co najmniej 4-cyfrowej mantysy
≈   3.66  	czyli 3.66e+00, to najbliższa liczba dająca się zapisać za pomocą mantysy 3-cyfrowej
Rysunek: błąd obcięcia przy dodawaniu liczb różnych rzędów

Precyzja obliczeń w odniesieniu do danych zmiennopozycyjnych związana jest z liczbą cyfr przeznaczoną na przechowywanie mantysy i wykładnika. Standardy międzynarodowego Instytutu Inżynierów Elektryków i Elektroników (IEEE) z 1985 roku wyróżniają dane zmiennopozycyjne:

Szczegółowe dane przytoczono jedynie dla ogólnej orientacji; proszę się ich nie uczyć na pamięć. Rezygnacja z warunku normalizacji pozwala nieco rozszerzyć te granice. Graniczne wartości w systemie dziesiętnym mają charakter szacunkowy.

Stosowane są także inne niestandardowe rozwiązania techniczne.

Na przykład wartością wyrażenia 1.0/3 w arytmetyce zmiennopozycyjnej jest 0.3333333333333333. Nie istnieje możliwość zapamiętania dłuższego ciągu cyfr rozwinięcia dziesiętnego ani dwójkowego tej liczby. Ewentualne żądania zwiększenia dokładności spowodują pojawianie się zer (arkusze kalkulacyjne) lub bezsensownych cyfr wynikających z zamiany przedstawienia liczb z dwójkowych na dziesiętne (Python).

Na przykład wartość 20! wynosi 2432902008176640000. Iloczyn wartości 1.0*2.0*…*19.0*20.0 daje wartość 2.43290200817664e+18, zgodną z rzeczywistą wartością 20!. Jednak liczba 20! + 1, czyli 2432902008176640001, nie może już być zapisana cyfrowo jako standardowa dana zmiennopozycyjna, gdyż mantysa musiałaby mieć 19 cyfr, podczas gdy w użytym systemie ma ich tylko 16. Zatem w arytmetyce komputerowej 2.43290200817664e+18 + 1.0 równa się 2.43290200817664e+18.

W przypadku liczby 32!, czyli 263130836933693530167218012160000000, czyli 2.6313083693369353016721801216⋅1035, liczba cyfr przekracza pojemność mantysy. Zatem liczby tej nie da się zapamiętać jako danej zmiennopozycyjnej bez straty informacji. Odpowiednia wartość wynosi 2.6313083693369352e+35, czyli po przekształceniu z postaci półlogarytmicznej do dziesiętnej 263130836933693520000000000000000000.0. Błąd bezwzględny wynosi w tym przypadku 10167218012160000000, czyli jest ogromny. Jednak jego wartość względna w porównaniu z prawdziwą wartością 32! wynosi zaledwie 3.8639401336005487⋅10−17.

Powyższe przykłady wydają się „kosmiczne”, ale miejmy na uwadze, że źle zaprojektowane obliczenia mogą prowadzić do operacji na liczbach znacznie różniących się rzędami wielkości.

Warto dodać, że działania na danych zmiennopozycyjnych nie spełniają praw arytmetyki „prawdziwych” liczb. Czasami bywa to zaskakujące.

W następującym przykładzie (por. D. Knuth, Sztuka programowania, t. 2., str. 246) kolejność grupowania działań ma istotny wpływ na wyniki:

# rachunki pojedynczej precyzji
  1.111111300E+07 + (-1.111111100E+07  + 7.511111259E+00) = 1.111111300E+07 - 1.111110300E+07 = 1.000000000E+01
( 1.111111300E+07 - 1.111111100E+07  ) + 7.511111259E+00  = 2.000000000E+00 + 7.511111259E+00 = 9.511111259E+00

# rachunki podwójnej precyzji
  1.111111300000000E+007 + (-1.111111100000000E+007  + 7.511111100000000E+000) = 1.111111300000000E+007 - 1.111110348888890E+007 = 9.511111099272966E+000
( 1.111111300000000E+007 -   1.111111100000000E+007) + 7.511111100000000E+000  = 2.000000000000000E+000 + 7.511111100000000E+000 = 9.511111100000001E+000

Proszę przeprowadzić samodzielnie te obliczenia w dowolnym dostępnym środowisku obliczeniowym. Dlaczego w pierwszym z czterech przykładów błąd sięga wartości 0.5, czyli przekracza 5% wyniku?

Jak widać, niekorzystnie zaprojektowane obliczenia numeryczne mogą doprowadzić do katastrofy (a przynajmniej do bezwartościowych wyników) bez jakiejkolwiek sygnalizacji błędu, a często także bez świadomości niewykształconego pod względem numerycznym użytkownika.

Do typowych błędów projektowania obliczeń należą: przyjęcie niekorzystnego układu jednostek, niekorzystne wyznaczenie umownego miejsca zerowego w układzie współrzędnych, niepotrzebne skalowanie przez bardzo duże lub bardzo małe liczby liczby (np. liczbę Avogadra czy choćby liczbę sekund w ciągu stulecia) wynikające często z bezkrytycznego stosowania gotowych wzorów. Skuteczne unikanie negatywnych efektów błędów numerycznych jest na ogół bardzo trudnym zadaniem.

Dodatkowy problem związany ze stosowaniem standardowych liczb zmiennopozycyjnych jest związany z faktem, że obliczenia prowadzone są w systemie dwójkowym. W związku z tym niektóre ważne liczby dziesiętne, np. 0.1, są w nich reprezentowane z błędem. Niektóre systemy użytkowe maskują ten fakt przed użytkownikiem, jednak tylko częściowo (i bywa to przyczyną nieporozumień).

Omówione właściwości obliczeń zmiennopozycyjnych mają istotne konsekwencje praktyczne dla projektowania obliczeń. Szczególną uwagę trzeba zachować przy interpretacji wartości wynikowych. Przypuśćmy, że pragniemy przyrównać wynik obliczeń a z pewną wartością b. Niestety, wskutek błędów arytmetyki zmiennopozycyjnej, oczekiwanie że wartość obliczona będzie równa wartości teoretycznie otrzymanej w rachunkach na papierze, nie jest uzasadnione. Zatem sprawdzanie, czy a = b, w przypadku wyników obliczeń zmiennopozycyjnych jest nierozsądne. Zamiast tego należy raczej zastanowić się, jakiej wielkości błąd może się pojawić, oraz jaka dokładność wyników nas zadowala. Pierwsze z tych oszacowań może być trudne. Kiedy jesteśmy w stanie odpowiedzieć na te pytania, wybierzmy liczbę dodatnią ε, która będzie co najmniej o rząd wielkości większa od możliwego błędu arytmetyki, i co najmniej o rząd wielkości mniejsza od wymaganej precyzji wyników. Wtedy sprawdzanie warunku a = b zastąpimy sprawdzaniem, czy |a − b| < ε.

Na przykład, jeśli w danej sytuacji szacujemy, że błędy arytmetyki pojawią się na 12 miejscu dziesiętnym, zaś zadowala nas dokładność do 6 miejsca dziesiętnego, rozsądnie będzie przyjąć ε = 1.0e−10. Pełne omówienie zagadnień związanych z przybliżonym charakterem arytmetyki zmiennopozycyjnej jest zbyt długie i zbyt złożone, by je tu zamieszczać. Zajmuje się tym dyscyplina z pogranicza informatyki i matematyki stosowanej, zwana analizą numeryczną.

Specjalna wartość NaN (Not a Number, ‘nie-liczba’) zarezerwowana jest dla przedstawiania wyników nieudanych operacji arytmetycznych. Jej konkretna postać w notacji zmiennopozycyjnej jest czysto umowna, wobec czego nie ma potrzeby jej przytaczania. Pojawienie się wartości NaN w obliczeniach jest zazwyczaj niepożądane, jednak niektóre środowiska pozwalają jej jawnie używać.

Liczby o dowolnej precyzji

Arytmetyka o dowolnej precyzji bywa stosowana w specjalistycznych środowiskach obliczeniowych. Użytkownik ma w nich możliwość określenia liczby cyfr służących do reprezentowania danych liczbowych poszczególnych typów. Dotyczy to zarówno modelu stałopozycyjnego (liczba cyfr całej liczby i jej części ułamkowej), jak zmiennopozycyjnego (liczba cyfr mantysy i wykładnika). Mimo to obliczenia w takich systemach nadal pozostają niedokładne z przyczyn zasadniczych.

Czas i data

Dane dotyczące daty i czasu tak naprawdę są danymi liczbowymi, wskazującymi położenie pewnego punktu (chwila, data) lub miary odcinka (czas trwania) na osi liczbowej reprezentującej czas. Poszczególne systemy mogą się różnić wyborem jednostki podstawowej oraz punktu zerowego na tej osi. W wielu systemach użytkowych (np. arkusze kalkulacyjne) przyjęto konwencję, zgodnie z którą liczba 1.0 wyraża czas trwania równy jednej dobie. Miejsce zerowe osi czasowej jest ustalone umownie (np. jako północ dnia 30 grudnia 1899 r.).

Powyższa konwencja gwarantuje, że czas trwania liczony w dobach, jako liczba rzeczywista jest równy liczbie dni.

Inna konwencja związana z notacją czasu jest stosowana w systemach operacyjnych podczas realizacji czynności systemowych, np. przy dostępie do pliku oraz w komunikacji sieciowej. Wyraża ona liczbę sekund, jakie upłynęły od umownej chwili początkowej (np. od 1 stycznia 1970 r.). Jest to tzw. timestamp (‘sygnatura czasu’, ‘datownik’).

Prezentacyjne formaty danych dotyczących daty i czasu są regulowane normą międzynarodową ISO-8601, ostatnio aktualizowaną w roku 2004.

Liczby wymierne

Jak wiadomo, liczby wymierne są ilorazami liczb całkowitych. Każdą liczbę wymierną da się przedstawić w postaci nieskracalnego ułamka, którego licznik i mianownik są liczbami całkowitymi. Taki też jest narzucający się sposób reprezentacji cyfrowej liczb wymiernych.

Zaletą arytmetyki cyfrowej danych wymiernych jest dokładność czterech podstawowych działań arytmetycznych, ze wszystkimi zastrzeżeniami wskazanymi podczas omawiania arytmetyki danych całkowitoliczbowych. Wadami są: duża pracochłonność (przy redukcji ułamków niezbędny jest rozkład liczb liczb na czynniki) i konieczność przechowywania bardzo dużych liczb całkowitych pojawiających się podczas operacji na ułamkach.

Z tych powodów dane typu wymiernego są dostępne głównie w specjalistycznych środowiskach obliczeniowych, w których dokładność jest ważniejsza od szybkości działania. Jednak kiedy w toku przetwarzania pojawią się liczby niewymierne, na przykład wskutek pierwiastkowania, dokładność obliczeń nie zostanie utrzymana, gdyż wyniki będą danymi typu rzeczywistego (najczęściej będzie to typ zmiennopozycyjny).

Do języków posiadających wbudowaną obsługę danych wymiernych należą m.in. LISP oraz Python.

Liczby zespolone

Liczby zespolone łatwo jest utożsamiać z parami liczb rzeczywistych; taki też jest najprostszy sposób ich reprezentacji cyfrowej. W niektórych systemach użytkowych i językach programowania można spotkać specjalny typ zespolony. Operacje na danych tego typu są realizowane za pomocą specjalnie zaprojektowanych procedur (np. w arkuszach kalkulacyjnych) lub tradycyjnie oznaczanych działań arytmetycznych (np. w języku Python), które w gruncie rzeczy sprowadzają się do działań arytmetycznych na parach liczb zmiennopozycyjnych. Wszystkie uwagi dotyczące dokładności działań zmiennopozycyjnych przenoszą się więc na dane zespolone.

Dane tekstowe

Ważnymi typami danych są znaki tekstu oraz ciągi znaków, zwane też łańcuchami, tekstami lub napisami. Dane tekstowe mogą, choć nie muszą, mieć określone ograniczenie liczby znaków.

W wyrażeniach stałe tekstowe są umieszczane wewnątrz ograniczników; rolę tę zazwyczaj pełnią pojedyncze apostrofy techniczne: 'napis' lub podwójne cudzysłowy techniczne: "napis".

W dobie powszechnego stosowania UNICODE w zasadzie istnieje możliwość przechowywania w danych tekstowych dowolnych znaków. Jednak w poszczególnych sytuacjach może zachodzić potrzeba przekodowania napisu z jednego kodowania do innego.

Podstawową operacją wykonywaną na danych tekstowych jest ich łączenie. Jej wynikiem oczywiście również dana tekstowa. Zawiera ona połączenie napisów składowych; w sytuacji kiedy system wymusza ograniczenie długości ciągu znaków, będzie ona obcięta do maksymalnej dozwolonej liczby znaków. Operator łączenia tekstów bywa, w zależności od środowiska i języka, oznaczany jednym z symboli: +, ++, &, | albo ||.

Operacją w pewnym sensie odwrotną do łączenia jest pobieranie fragmentu tekstu. Zazwyczaj korzysta się z tego, że znaki w napisie są numerowane (począwszy od zera lub jedynki, zależnie od systemu).

Przy porównywaniu napisów bierze się pod uwagę tzw. porządek słownikowy, czyli kolejność w jakiej hasła zostałyby umieszczone w uporządkowanym alfabetycznie spisie. Wynika on jednoznacznie z kolejności znaków w alfabecie obejmującym wszystkie dostępne symbole. Zatem zdanie 'Warszawa' < 'Zduńska Wola' ma wartość True, i to nie dlatego, żeby Zduńska Wola jako miasto była większa od Warszawy, tylko dlatego, że początkowa litera 'Z' zostanie w spisie umieszczona za 'W'. Podobnie prawdziwe jest zdanie 'Warszawa' < 'Wrocław'; w tym przypadku pierwsze litery są jednakowe, więc do ustalenia kolejności niezbędne jest porównanie znaków 'a' i 'r'.

Zwracamy też uwagę na różnice między liczbami a ich tekstowym przedstawieniem. Wartości liczbowe 1.0, 01.0 i 1.00 są sobie równe. Napisy '1.0', '01.0' i '1.00' równe nie są. Wynikiem działania 1.0 + 2.0 jest liczba 3.0. Wynikiem działania '1.0' + '2.0' jest napis '1.02.0'. Wynikiem porównania 10 < 2 jest wartość F (bo 10 jest większe od 2). Wynikiem porównania '10' < '2' jest wartość T (bo '10' zaczyna się znakiem '1', który w spisie umieszcza się przed znakiem '2').

Porównanie 10 < '2' nie ma sensu, gdyż dotyczy danych niezgodnych typów (istnieją języki i środowiska, które w takich przypadkach automatycznie dokonują konwersji; istnieją także takie, w których wynik takiej operacji nie zależy od porównywanych wartości).

Przy prezentacji tekstowej liczb dziesiętnych należy też pamiętać o właściwym znaku separatora części ułamkowej. W arkuszach kalkulacyjnych ciąg cyfr rozdzielony znakiem innym niż obowiązujący zostanie zinterpretowany jak tekst. W językach programowania zapis taki prowadzi do błędu.

Na wzmiankę zasługują także inne operacje często wykonywane na tekstach. Nie mają one swoich odpowiedników w standardowych symbolach działań, ale są na tyle ważne, że wiele środowisk udostępnia je w postaci gotowych funkcji.

Należy do nich znajdowanie położenia podanego ciągu znaków w tekście. Operacja ta zwraca wynik liczbowy wskazujący na numer znaku, od którego rozpoczyna się dopasowany fragment. Natomiast wynikiem operacji zastępowania jest ciąg znaków, w którym wszystkie wystąpienia zadanego wzorca zamieniono na inny podany tekst. W obu wymienionych operacjach wyszukiwany tekst może być zadany wprost lub przez wyrażenie regularne. Inną ważną operacją tego typu jest podział tekstu na krótsze fragmenty oddzielone od siebie wybranym symbolem lub frazą, np. znakiem tabulacji, znakiem końca wiersza albo ciągiem znaków spacji.

Uwagi o zgodności typów

Czy da się przyrównywać do siebie, lub porównywać ze sobą, dane różnych typów (np. pogodę i wykształcenie)? Z jednej strony widać, że skoro typy są różne, to obiekty tym bardziej. Z drugiej, sens logiczny takiej operacji jest wątpliwy: one są nieporównywalne.

Istnieje ważny wyjątek: kiedy dane opisują de facto porównywalne obiekty, ale sposoby ich opisu są odmienne.

Z banalnym przypadkiem tego typu mamy do czynienia na przykład, kiedy jedna osoba podała wzrost w centymetrach, inna zaś — w calach. W tym przypadku dane są przechowywane tym samym sposobem (jako liczby dziesiętne), ale ich porównanie wymaga wstępnej obróbki.

Inny przypadek związany jest z sytuacją, kiedy wzrost jednej osoby jest wyrażony w centymetrach jako liczba całkowita, a drugiej — również w centymetrach, ale w postaci danej zmiennopozycyjnej. Jak już wiemy, dane całkowitoliczbowe i rzeczywiste różnią się sposobem przechowywania w systemach komputerowych. Porównanie takich danych jest sensowne, ale wymaga sprowadzenia ich do jednego sposobu — pytanie, jakiego.

W opisanych wyżej sytuacjach zachodzi potrzeba zamiany wartości pewnego typu na odpowiadające im wartości tego samego albo innego typu. Proces takiej zamiany nosi nazwę konwersji.

Jeżeli dopuścimy konwersję danych, niektóre przyrównania i porównania danych różniących się typem zyskają sens. Trzeba jednak zapytać, jakie rodzaje konwersji są dopuszczalne. Z przeprowadzeniem konwersji wiąże się niebezpieczeństwo utraty pewnej części informacji (tak będzie np. w przypadku zastąpienia danych dziesiętnych wartościami całkowitoliczbowymi).

Niektóre konwersje (np. dotyczące jednostek miary) powinny być kontrolowane przez użytkowników, niektóre zaś (np. dotyczące standardowych typów danych) są przeprowadzane w razie potrzeby automatycznie i zarządzane na poziomie języka albo środowiska użytkowego.

Istnieją języki programowania oraz środowiska bardziej i mniej liberalne pod tym względem. Niektóre języki pozwalają przyrównywać i porównywać tylko dane typów zgodnych ze sobą (tzw. silna kontrola zgodności typów); w przypadku niezgodności generując błąd. Inne — wśród nich Python, a także niektóre środowiska arkuszy kalkulacyjnych — zezwalają na przyrównywanie i porównywanie danych dowolnych typów. Jednak wyniki takich operacji trzeba umieć zinterpretować, gdyż nie muszą być zgodne z potocznymi oczekiwaniami.

Pytania kontrolne

  1. Wymień i scharakteryzuj najważniejsze typy danych prostych
  2. Podaj „wzięte z życia” przykłady danych prostych i przypisz im odpowiedni typ
  3. Scharakteryzuj typy liczbowe ze względu na dokładność wykonywanych na nich operacji arytmetycznych
  4. Podaj przykłady operacji na danych liczbowych, prowadzących do błędu wykonania
  5. Podaj przykłady konwersji typów danych mające praktyczne znaczenie
© Copyright 2000–2011 by Jan Jełowicki, Katedra Matematyki Uniwersytetu Przyrodniczego we Wrocławiu
Ostatnia modyfikacja w listopadzie 2011 r.
janj@aqua.up.wroc.pl
http://karnet.up.wroc.pl/~jasj