3. diel - Reprezentácie čísel v počítači
V minulej lekcii, Prenos bitov aneb od pántov vedú drôty ... , sme sa pozreli na to, ako sa prenáša bity a bajty. Teraz sme v situácii, keď už sme nejako dostali bity odniekiaľ niekam vo forme dátového prúdu. Dokonca sme si aj určili, že dáta nasekáme na bloky. Stále to sú ale len nuly a jednotky. A ak s nimi chceme niečo robiť, musíme sa sa rozhodnúť, ako prevedieme túto sekvenciu na čísla. Ak sa vám až doteraz zdalo, že sa tu bavíme len o teoretických veciach, tak dnes nadobudnú konkrétnu podobu. Ukážeme si, aké sú s číslami a ich prevody problémy
Reprezentácie celých čísel
Začneme celými číslami, ktoré sú samozrejme pre ukladanie najjednoduchšie.
Prirodzená reprezentácie čísiel (unsigned)
Majme čísla, ktoré pre jednoduchosť posielame po 1 bajtu = 8 bitov, teda
8 pozícií pre číslicu 0
alebo 1
. Hodnota
0000000 = 0
, 11111111 = 255
. Táto reprezentácia je
bez problémov. Vlastne, malý zádrhel tu je. Ako povedať, že chceme číslo
-1
? Nemáme žiadny znak pre -
, posielajú sa iba
0
a 1
. Preto sa táto reprezentácia nazýva
unsigned = bezznaménková. Záporné čísla v praxi ale
bohužiaľ často potrebujeme reprezentovať.
Bias (posun)
Tak dobre, chceme aj záporné čísla, tak si môžeme rozsah posunúť.
Kladnú časť skrátime na polovicu a tak dokážeme uložiť hodnoty od
-128
do 127
namiesto od pôvodných 0
až
255
. Určilo sa, že nula bude kladná, takže je pomer +/-
zachovaný. Ku skutočnej hodnote sa pred uložením vždy pripočíta
128
a pri načítaní zas odpočíta.
Ukážme si niekoľko príkladov, ako rôzne čísla uložíme:
-128 = 0, -127 = 1, ... , -1 = 127, 0 = 128, 1 = 129, ..., 127 = 255
Pri prepísanie do binárneho zápisu tento posun vyzerá takto:
-128 = 00000000, -127 = 00000001, ..., -1 = 01111111, 0 = 10000000, 1 = 10000001, ..., 127 = 11111111
Táto reprezentácia je pomerne využívaná, má ale jeden háčik. Keď sa
pozriete, tak 0
nie je uložená ako 00000000
. A to je
celkom divné, úplne proti prirodzenému vnímania
Znamienkový bit
Skúsme záporné číslo reprezentovať ešte inak. Povedzme, že si
vyhradíme najvyššie bit na tzv. Sign bit (znamienkový bit). 1
znamená záporné, 0
znamená kladné. Pre reprezentáciu čísla
sa teda dostávame už len na 7 číslic a teda na maximálnu hodnotu
127
. Máme opäť možnosť reprezentovať -128
až
127
. Vlastne nie tak celkom. Náš rozsah je teraz od
-127
do 127
, ukážeme si prečo:
-127 = 11111111, ..., -1 = 1000001, 0 = 00000000, 0 = 10000000, 1 = 00000001, ..., 127 = 01111111
Všimli ste si? Prvý mucha tejto reprezentácie je, že máme
+0
a -0
. To nám ubralo to jedno záporné číslo,
ktoré môžeme reprezentovať. Navyše je to neintuitívne. Čas od času nuly
porovnávame. +0 ?= -0
...? Rovnajú sa, alebo nie? Má to tiež
pár ďalších škaredých vlastností. V bitovom zápise je
-127 > -126
. No, ale nemôžeme chcieť všetko. Ten nápad je
ale celkom dobrý, nešlo by aspoň trochu túto reprezentáciu zlepšiť?
Jednotkový doplnok
Jednotkový doplnok sa snaží riešiť problém, že sú "zápornejší" čísla binárne väčšie. Ak teda chceme záporné číslo, použime doplnok (anglicky ones 'complement). Na také číslo prevedieme tzv. Negáciu a všetky bity obrátime:
-127 = 10000000, -126 =10000001, ..., -1 = 11111110, 0 = 00000000, 0 = 11111111, 1 = 00000001, ..., 127 = 01111111
Už je to oveľa lepšie, teraz naozaj platí, že
-127 < -126
. Len sa nám opäť zduplikovala 0
.
Navyše celkom nepekne. Každopádne už sme vyriešili jeden problém.
Nemôžeme vyriešiť ešte jeden? No, keď sa tak hlúpo pýtam ...
Dvojkový doplnok
Trochu zmätené meno, ale jednotkový bol už použitý. Predstavte si, že čísla budeme reprezentovať pomocou jedničkového doplnku ak tomu prirátame jedničku. Kladné čísla zostanú rovnako kladnými a záporné čísla budú mať pripočítanou jedničku.
-128 = 10000000, -127 = 10000001, -126 =10000010, ..., -1 = 11111111, 0 = 00000000, 1 = 00000001, ..., 127 = 01111111
Pre ilustráciu prikladám obrázok:
Vuali, 2 nuly zmizli a nakoniec sme mohli reprezentovať aj hodnotu
-128
. Tu to teoreticky trochu "hapruje", pretože nemôžete
reprezentovať 128
, aby ste si sami vykonali túto operáciu, ale
na iných číslach to funguje. Navyše máme stále sign bit zachovaný.
Využitie jednotlivých reprezentácií v praxi
Kde sa ktorá reprezentácie používa? Sem tam sa používa unsigned, pomerne často Bias a najčastejšie je dvojkový doplnok, zvlášť u desatinných čísel, o tom však až za chvíľu.
Pretečeniu
Len ešte poznámečka, než sa dostaneme k tomu, že "chceme viac". Ponúka
sa otázka, čo sa stane, keď k 127
pripočítame 1
.
No, 0111111 + 00000001 = 10000000 = -127
. Také pekné, malé,
nenápadné pretečeniu. Dávajte si na to pozor, je to nenápadná chybička a
nikto vám nič nepovie. Pre počítač je to úplne validný inštrukcie a je
možné, že ste to tak chceli. Kontrolujte si svoje typy, v dnešných
pamätiach nehrá úspora takú rolu, aby ste museli šetriť každý kus.
Píšte kód zrozumiteľne, prehľadne a bezpečne. (Na druhej strane predsa len
nie je úplne rozumné ukladať vek človeka v Long )
Reprezentácie väčších čísel
Čo keď chceme väčšie čísla než 127
? Mimochodom, tento
číselný typ existuje ako byte
. Môžeme predsa mať napríklad
typ int
od -2147483648
do 2147483649
.
Ďalšie typy sa výrazne líši u každej implementácie jazyka. Java napr. Unsigned nemá, ale v C # je
možné mať aj uint
(unsigned int) v intervale
<0, 4294967295>
. Existujú aj short
,
long
, char
a ďalšie typy. Tie ale nechajme
bokom.
Prevádzanie medzi typmi
Používajme dvojkový doplnok, ktorý je najbežnejšie a možno aj vďaka týmto prevodom sa najlepšie osvedčil. Pre ďalšie prevody budem predpokladať, že zväčšujeme či zmenšujeme rozsah dvakrát. Čisto z praktických dôvodov, stránka má obmedzenú šírku. V zátvorkách sú uvedené príklady takýchto konverzií.
Prevod z menšieho rozsahu čísla na väčšie (int => long)
Ak je číslo kladné alebo záporné, rozkopírujeme signed bit:
1B = 8b | 2B = 16b | desiatkový zápis |
00000001 | 0000000000000001 | 1 => 1 |
10000001 | 1111111110000001 | -127 => -127 |
Ak je číslo v rozsahu toho menšieho, či už kladné, alebo záporné, nie je problém. Ak je však číslo vo väčšom rozsahu než menšie z nich, máme problém ...
2B = 16b | 1B = 8b | desiatkový zápis |
1111111110011001 | 10011001 | -147 => -147 |
0000000000000110 | 00000110 | 6 => 6 |
1110111110011001 | 10011001 | -4199 => -147 |
0001100000000000 | 00000000 | 6144 => 0 |
Reprezentácie reálnych čísel
Keď sme sa rozohriali, prejdime rovno na reprezentáciu reálnych čísel.
Musíme si totiž uvedomiť, čo vlastne chceme. Delili ste niekedy na
kalkulačke číslo 10 / 3 * 2
? Vyšlo vám väčšinou niečo ako
6,66666666667
. Matematicky je to nezmysel, lenže počítač v sebe
nemá nič nekonečné. Čokoľvek, čo robí, robí s konečnou pamäťou a
konečným časom. Proste nemáme niekoľko gigabajtov voľného priestoru len
na uloženie (a to ešte nepresného) výsledku. Napriek tomu ale môžeme
reprezentovať desetinnáa čísla pomerne slušne. Možno vás napadlo
1100,01
= 12,25
. Čiarku ale samozrejme tiež nemáme.
Čo s tým?
Pevná desatinná čiarka
Môžeme si určiť, že desatinná čiarka bude tam a tam. Napr., Že 3
cifry budú vždy za desatinnou čiarkou a teda 5 cifier zostane na číslo. Pre
jednoduchosť uvažujme unsigned typ (teda nezáporné číslo). Takto by sme
zapísali napr. Čísla 00101,001 = 5,25
a 01101,001
=
13,5. (Desatinná čiarka je v binárnom zápise samozrejme len pre nás pre
prehľadnosť).
Pre lepšiu zápis môžeme čísla ukladať ako mocniny dvojky (v
exponenciálnom tvare, tzv. Mantisa), teda ako 1,101001 * 2^3
.
Stále sa ale jedná o veľmi podobný zápis ako sú Integer, len mám v
nejakom protokole zaznamenané, ako desatinné čísla vyzerajú.
Sčítanie funguje dobre, máme pri sebe blízko podobne veľké čísla a všetko je v poriadku. Čo keď ale chceme počítať s číslami, bez toho aby sme vopred poznali ich rozsah? Ak počítame izbovú teplotu, môžeme si pevnú desatinnú čiarku dovoliť. Čo ale keď budeme chcieť počítať finančné transakcie a zrovna na potvoru sa na bankový aplikáciu prihlási Bill Gates a Jenda Podšívka z Horných šišiek u Dubenic ...
Plávajúce desatinná čiarka
U plávajúce desatinnej čiarky si priamo povieme, koľko bajtov sa používa pre Mantis (číslo bez desatinnej čiarky) a exponent (čím násobíme Mantis). Pre 8 bitov sa používa práve 1 sign bit, 2 bity exponent a 5 bitov mantisa. Zvyšok zahodíme. Z toho vyplýva, že desatinné čísla nie sú moc presná. Prečo sa tomu hovorí plávajúce? Aneb pretože nemáme pevne zadrôtovanou, kde v tom čísle desatinná čiarka bude.
Tu si môžeme všimnúť ešte jednej zvláštnej hodnoty, NaN
.
V desatinných číslach totiž ide deliť nulou ... Áno, celý vesmír sa
rúca, ale nie tak celkom. Proste dostaneme výsledok "Not a number", čo je
také príjemné číslo. Nemôžete s ním robiť nič a akákoľvek operácia
s ním vám vyhodí znova NaN
. Pre ukážku máte ešte jeden
obrázok ukazujúci typy desatinných čísel. (Float a double):
Dokonca si môžeme dovoliť prvú jednotku vôbec nepísať, čím
spresníme zápis čísla. Každé číslo sa skladá zo sign bitu, mantisy a
exponentu. Mantisa môže byť vo dvojkovom doplnku a v exponentu sa používa
Bias (pozri vyššie). Keďže máme Bias, exponent môže byť aj záporný,
čo znamená reálne čísla od 0
do 1
či od
-1
do 0
. Rozdiel jasnejšie opíše tabuľka
nižšie:
Pozn .: Shared exponent tu má zmysel, pretože všetky fixed point čísla majú "rovnako hodnotný" prvý bit.
Pozor!
Keď sa pozriete na tabuľku vyššie, tak plávajúce desatinná čiarka je
očividne veľmi nepresná notácie. Veľa bitov zahadzuje. Nikdy, naozaj nikdy
preto nepoužívajte na porovnávanie float
či double
čísel ==
. Ak máte číslo a a
číslo
b
, tak robte niečo ako a – b < 0.0001
či niečo
podobné. Môže sa stať, že sa čísla jednoducho o kúsok mínou a podmienka
neprejde, aj keď by ste očakávali, že áno. Ak môžete, používajte celé
čísla. A ak z toho máte v hlave guláš, vedzte, že nebudete prvý, ani
posledný. Tak hlavu hore
V budúcej lekcii, Babylonskej zmätenie kódovanie , sa pozrieme pre zmenu na reprezentáciu textu.