Body zdarma Java týden
Využij podzimních slev a získej od nás až 40 % bodů zdarma! Více zde
Pouze tento týden sleva až 80 % na Java e-learning!

Lekce 3 - Reprezentace čísel v počítači

Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem.
Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, Přenos bitů aneb od pantáty vedou dráty..., jsme se podívali na to, jak se přenáší bity a bajty. Nyní jsme v situaci, kdy už jsme nějak dostali bity odněkud někam ve formě datového proudu. Dokonce jsme si i určili, že data nasekáme na bloky. Pořád to jsou ale jen nuly a jedničky. A pokud s nimi chceme něco dělat, musíme se se rozhodnout, jak převedeme tuto sekvenci na čísla. Pokud se vám až doteď zdálo, že se tu bavíme pouze o teoretických věcech, tak dnes nabudou konkrétní podobu. Ukážeme si, jaké jsou s čísly a jejich převody problémy :)

Reprezentace celých čísel

Začneme celými čísly, která jsou samozřejmě pro ukládání nejjednodušší.

Přirozená reprezentace čísel (unsigned)

Mějme čísla, která pro jednoduchost posíláme po 1 bajtu = 8 bitů, tedy 8 pozic pro číslici 0 nebo 1. Hodnota 0000000 = 0, 11111111 = 255. Tato reprezentace je bez problémů. Vlastně, malý zádrhel tu je. Jak říci, že chceme číslo -1? Nemáme žádný znak pro -, posílají se pouze 0 a 1. Proto se tato reprezentace nazývá unsigned = bezznaménková. Záporná čísla v praxi ale bohužel často potřebujeme reprezentovat.

Bias (posun)

Tak dobře, chceme i záporná čísla, tak si můžeme rozsah posunout. Kladnou část zkrátíme na polovinu a tak dokážeme uložit hodnoty od -128 do 127 namísto od původních 0255. Určilo se, že nula bude kladná, takže je poměr +/- zachován. Ke skutečné hodnotě se před uložením vždy přičte 128 a při načtení zas odečte.

Ukažme si několik příkladů, jak různá čísla uložíme:

-128 = 0,
-127 = 1,
... ,
-1 = 127,
0 = 128,
1 = 129,
...,
127 = 255

Při přepsání do binárního zápisu tento posun vypadá takto:

-128 = 00000000,
-127 = 00000001,
...,
-1 = 01111111,
0 = 10000000,
1 = 10000001,
...,
127 = 11111111

Tato reprezentace je poměrně využívaná, má ale jeden háček. Když se podíváte, tak 0 není uložena jako 00000000. A to je docela divné, úplně proti přirozenému vnímání :)

Znaménkový bit

Zkusme záporné číslo reprezentovat ještě jinak. Řekněme, že si vyhradíme nejvyšší bit na tzv. sign bit (znaménkový bit). 1 znamená záporné, 0 znamená kladné. Pro reprezentaci čísla se tedy dostáváme již jen na 7 číslic a tudíž na maximální hodnotu 127. Máme opět možnost reprezentovat -128127. Vlastně ne tak docela. Náš rozsah je nyní od -127 do 127, ukážeme si proč:

-127 = 11111111,
...,
-1 = 1000001,
0 = 00000000,
0 = 10000000,
1 = 00000001,
...,
127 = 01111111

Všimli jste si? První moucha této reprezentace je, že máme +0 a -0. To nám ubralo to jedno záporné číslo, které můžeme reprezentovat. Navíc je to neintuitivní. Čas od času nuly porovnáváme. +0 ?= -0...? Rovnají se, nebo ne? Má to také pár dalších ošklivých vlastností. V bitovém zápisu je -127 > -126. No, ale nemůžeme chtít všechno. Ten nápad je ale docela dobrý, nešlo by alespoň trochu tuto reprezentaci zlepšit?

Jedničkový doplněk

Jedničkový doplněk se snaží řešit problém, že jsou "zápornější" čísla binárně větší. Chceme-li tedy záporné číslo, použijme doplněk (anglicky ones' complement). Na takové číslo provedeme tzv. negaci a všechny bity obrátíme:

-127 = 10000000,
-126 =10000001,
...,
-1 = 11111110,
0 = 00000000,
0 = 11111111,
1 = 00000001,
...,
127 = 01111111
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

Už je to mnohem lepší, teď opravdu platí, že -127 < -126. Jen se nám opět zduplikovala 0. Navíc docela nehezky. Každopádně už jsme vyřešili jeden problém. Nemůžeme vyřešit ještě jeden? No, když se tak hloupě ptám...

Dvojkový doplněk

Trochu zmatené jméno, ale jedničkový byl už použit. Představte si, že čísla budeme reprezentovat pomocí jedničkového doplňku a k tomu přičteme jedničku. Kladná čísla zůstanou stejně kladnými a záporná čísla budou mít přičtenou jedničku.

-128 = 10000000,
-127 = 10000001,
-126 =10000010,
...,
-1 = 11111111,
0 = 00000000,
1 = 00000001,
...,
127 = 01111111

Pro ilustraci přikládám obrázek:

Dvojkový doplněk

Vualá, 2 nuly zmizely a nakonec jsme mohli reprezentovat i hodnotu -128. Zde to teoreticky trochu "hapruje", neboť nemůžete reprezentovat 128, abyste si sami provedli tuto operaci, ale na jiných číslech to funguje. Navíc máme pořád sign bit zachovaný.

Využití jednotlivých reprezentací v praxi

Kde se která reprezentace používá? Sem tam se používá unsigned, poměrně často Bias a nejčastější je dvojkový doplněk, zvlášť u desetinných čísel, o tom však až za chvíli.

Přetečení

Jen ještě poznámečka, než se dostaneme k tomu, že "chceme více". Nabízí se otázka, co se stane, když k 127 přičteme 1. No, 0111111 + 00000001 = 10000000 = -127. Takové pěkné, malé, nenápadné přetečení. Dávejte si na to pozor, je to nenápadná chybka a nikdo vám nic neřekne. Pro počítač je to naprosto validní instrukce a je možné, že jste to tak chtěli. Kontrolujte si své typy, v dnešních pamětích nehraje úspora takovou roli, abyste museli šetřit každý kus. Pište kód srozumitelně, přehledně a bezpečně. (Na druhou stranu přeci jen není úplně rozumné ukládat věk člověka v longu :) )

Reprezentace čísel ve dvojkové soustavě

Reprezentace větších čísel

Co když chceme větší čísla než 127? Mimochodem, tento číselný typ existuje jako byte. Můžeme přeci mít třeba typ int od -2147483648 do 2147483649. Další typy se výrazně liší u každé implementace jazyka. Java např. unsigned nemá, ale v C# je možné mít i uint (unsigned int) v intervalu <0, 4294967295>. Existují i short, long, char a další typy. Ty ale nechme stranou.

Převádění mezi typy

Používejme dvojkový doplněk, který je nejběžnější a možná i díky těmto převodům se nejlépe osvědčil. Pro další převody budu předpokládat, že zvětšujeme či zmenšujeme rozsah dvakrát. Čistě z praktických důvodů, stránka má omezenou šířku. V závorkách jsou uvedeny příklady takovýchto konverzí.

Převod z menšího rozsahu čísla na větší (int => long)

Je-li číslo kladné nebo záporné, rozkopírujeme signed bit:

1B = 8b 2B = 16b desítkový zápis
00000001 0000000000000001 1 => 1
10000001 1111111110000001 -127 => -127

Převod z většího rozsahu čísla na menší (long => int)

Je-li číslo v rozsahu toho menšího, ať už kladné, nebo záporné, není problém. Je-li však číslo ve větším rozsahu než menší z nich, máme problém...

2B = 16b 1B = 8b desítkový zápis
1111111110011001 10011001 -147 => -147
0000000000000110 00000110 6 => 6
1110111110011001 10011001 -4199 => -147
0001100000000000 00000000 6144 => 0

Pokud si nejste opravdu jistí, co máte v longu dosazené, pozor na převody!

Reprezentace reálných čísel

Když jsme se rozehřáli, přejděme rovnou na reprezentaci reálných čísel. Musíme si totiž uvědomit, co vlastně chceme. Dělili jste někdy na kalkulačce číslo 10 / 3 * 2? Vyšlo vám většinou něco jako 6,66666666667. Matematicky je to nesmysl, jenže počítač v sobě nemá nic nekonečné. Cokoliv, co dělá, dělá s konečnou pamětí a konečným časem. Prostě nemáme několik gigabajtů volného prostoru jen na uložení (a to ještě nepřesného) výsledku. Přesto ale můžeme reprezentovat desetinnáa čísla poměrně slušně. Možná vás napadlo 1100,01 = 12,25. Čárku ale samozřejmě také nemáme. Co s tím?

Pevná desetinná čárka

Můžeme si určit, že desetinná čárka bude tam a tam. Např., že 3 cifry budou vždy za desetinnou čárkou a tedy 5 cifer zbude na číslo. Pro jednoduchost uvažujme unsigned typ (tedy nezáporné číslo). Takto bychom zapsali např. čísla 00101,001 = 5,25 a 01101,001 = 13,5. (Desetinná čárka je v binárním zápisu samozřejmě jen pro nás pro přehlednost).

Pro lepší zápis můžeme čísla ukládat jako mocniny dvojky (v exponenciálním tvaru, tzv. mantisa), tedy jako 1,101001 * 2^3. Pořád se ale jedná o velmi podobný zápis jako jsou integery, jen mám v nějakém protokolu zaznamenáno, jak desetinná čísla vypadají.

Sčítání funguje dobře, máme u sebe blízko podobně velká čísla a vše je v pořádku. Co když ale chceme počítat s čísly, aniž bychom předem znali jejich rozsah? Pokud počítáme pokojovou teplotu, můžeme si pevnou desetinnou čárku dovolit. Co ale když budeme chtít počítat finanční transakce a zrovna na potvoru se na bankovní aplikaci přihlásí Bill Gates a Jenda Podšívka z Horních Koblížků u Dubenic...

Plovoucí desetinná čárka

U plovoucí desetinné čárky si přímo řekneme, kolik bajtů se používá pro mantisu (číslo bez desetinné čárky) a exponent (čím násobíme mantisu). Pro 8 bitů se používá právě 1 sign bit, 2 bity exponent a 5 bitů mantisa. Zbytek zahodíme. Z toho plyne, že desetinná čísla nejsou moc přesná. Proč se tomu říká plovoucí? Aneb protože nemáme pevně zadrátováno, kde v tom čísle desetinná čárka bude.

Plovoucí desetinná čísla

Zde si můžeme všimnout ještě jedné zvláštní hodnoty, NaN. V desetinných číslech totiž jde dělit nulou... Ano, celý vesmír se hroutí, ale ne tak docela. Prostě dostaneme výsledek "Not a number", což je takové příjemné číslo. Nemůžete s ním dělat nic a jakákoliv operace s ním vám vyhodí znovu NaN. Pro ukázku máte ještě jeden obrázek ukazující typy desetinných čísel. (float a double):

Typy desetinných čísel – float a double

Dokonce si můžeme dovolit první jedničku vůbec nepsat, čímž zpřesníme zápis čísla. Každé číslo se skládá ze sign bitu, mantisy a exponentu. Mantisa může být ve dvojkovém doplňku a v exponentu se používá Bias (viz výše). Jelikož máme Bias, exponent může být i záporný, což znamená reálná čísla od 0 do 1 či od -1 do 0. Rozdíl jasněji popíše tabulka níže:

Pozn.: Shared exponent tu má smysl, neboť všechny fixed point čísla mají "stejně hodnotný" první bit.

Pozor!

Když se podíváte na tabulku výše, tak plovoucí desetinná čárka je očividně velmi nepřesná notace. Spoustu bitů zahazuje. Nikdy, opravdu nikdy proto nepoužívejte na porovnávání float či double čísel ==. Pokud máte číslo a a číslo b, tak dělejte něco jako a – b < 0.0001 či něco podobného. Může se stát, že se čísla jednoduše o kousek minou a podmínka neprojde, i když byste očekávali, že ano. Pokud můžete, používejte celá čísla. A jestli z toho máte v hlavě guláš, vězte, že nebudete první, ani poslední. Tak hlavu vzhůru :)

V příští lekci, Babylonské zmatení kódování, se podíváme pro změnu na reprezentaci textu.


 

 

Článek pro vás napsal Tricerator
Avatar
Jak se ti líbí článek?
3 hlasů
Autor se věnuje teoretické informatice. Ve svých volných chvílích nepohrdne šálkem dobrého čaje, kaligrafickým brkem a foukací harmonice.
Předchozí článek
Přenos bitů aneb od pantáty vedou dráty...
Všechny články v sekci
Principy fungování počítačů
Miniatura
Následující článek
Babylonské zmatení kódování
Aktivity (5)

 

 

Komentáře

Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zatím nikdo nevložil komentář - buď první!