Maturitní otázka č. 5 - číselné soustavy

Popište obecný zápis celého čísla. Porovnejte zápis čísel v různých číselných soustavách (desítková, dvojková, osmičková, šestnáctková)

Snad není potřeba tuhle oblast rozebírat příliš podrobně. Obyčejně používáme poziční soustavy o nějakém přirozeném základu - to jsou všechny výše jmenované. V nich má každá číslice nějakou váhu danou právě pozicí, na které v čísle je: například třetí číslice zprava má v desítkové soustavě váhu 100. Když číslice označíme zprava a0...an, hodnota čísla vyjádřeného v soustavě o základu b je součet ai·bi, sečteno pro i od 0 do n. Nic překvapivého.

Čísla jsou obyčejně v desítkové soustavě. Když se v počítači vyskytuje číslo v šestnáctkové (hexadecimální) soustavě, má předponu 0x a pro číslice 10 až 15 používáme znaky A až F. Například 255 je 0xFF. Čísla v binární soustavě mají obdobně předponu 0b, tedy 0xFF je 0b11111111. Osmičková soustava se používá zřídka, o to je překvapivější její předpona: samotná 0, proto 0377 znamená ve skutečnosti v desítkové soustavě (opět) 255.

Když nás zajímá zápis nějakého čísla v soustavě o určitém základu, nejlepší je, když to číslo umíme rychle dělit se zbytkem. Pak stačí dělit zadané číslo základem b, zbytek zapisovat jako číslice výsledku zprava doleva (!) a dělení vždy opakovat pro výsledný podíl. Až se dostaneme na podíl 0, přestaneme.

Obdobný postup se dá použít, když máme číslo zapsané v nějaké soustavě a chceme jej nějak zpracovat. Zavedeme si nějakou proměnnou a na začátku ji nastavíme na nulu. Číslice čteme tentokrát zleva, každou do proměnné přičteme a tento součet vynásobíme základem b. To je mimochodem postup známý jako Hornerovo schéma - obecně velmi účinný algoritmus, který je dobré mít na paměti.

Všimněte si, že v obou případech nás nezajímá soustava jednoho z čísel - je důležité jen, abychom jej uměli rychle dělit nebo násobit. Z toho ohledu vlastně ani nemusíme vědět, že počítače pracují ve dvojkové soustavě... ale opravdu to tak je ;) Význam to může mít, pokud převádíme mezi soustavami, které mají soudělné základy. Obzvlášť, když je jeden základ mocninou druhého, například při převodu z dvojkové do šestnáctové soustavy. Potom nepotřebujeme žádné početní operace - každou čtveřici bitů na vstupu přeložíme na jedno šestnáctkové číslo (když počet číslic nevychází, dopíšeme zleva nuly). Na ukázku - 0b11 jsou 3 = 0x3, 0b1011 je 11 = 0xB. Proto 0b111011 je 0x3B.

Když převádíme mezi sousavami v hlavě (třeba z desítkové do binární), je možná pohodlnější odečítat postupně mocniny dvojky, protože dělení nám lidem prostě trvá dlouho. Ať vás ale nenapadne tímhle způsobem převádět čísla v počítači - pro větší čísla to je zbytečně pomalé.

Popište formáty čísel, se kterými pracují procesory a běžně používané typy čísel v některých programovacích jazycích. Zmiňte se o jejich výhodách a nevýhodách pro různé účely

První postřeh je, že čísla v procesoru mají vždy omezený rozsah. Když výsledek nějaké početní operace tenhle rozsah přeteče, vyjde nám nesmysl a je na nás, abychom na to dali pozor. Nadbytečné číslice se prostě ztratí a zůstanou jen těch několik posledních zprava. Většina jazyků nabízí jednobajtový typ (char nebo byte), do kterého tedy uložíme například číslo v rozsahu 0 až 255. Dále se obyčejně vyskytuje typ int, jehož rozsah ale bývá závislý na procesoru - na 32 bitových jsou to čtyři bajty, na 64 bitových osm. Jazyk C z něj odvozuje typ short int s polovičním počtem bajtů (rozsah je tedy odmocnina z rozsahu intu) a long int s dvojnásobným počtem bajtů. Některé jazyky (například Ruby a Python) to řeší za nás a rozsah nám zvýší podle potřeby zvýší na libovolnou hodnotu.

Dále si všimneme, že jsme se zatím věnovali jenom číslům přirozeným. Záporná čísla můžeme zavést různými způsoby, nejlepší je dvojkový doplněk, protože se hezky chová ke sčítání a odečítání. Když chceme rozšířit třeba typ char o záporná čísla, vejdou se tam hodnoty od -128 do +127. Čísla 0 až 127 převádíme beze změny, následující číslo je pak -128 a dále hodnoty stoupají až k -1. Stojí za to si vyzkoušet, že hodnoty v tomhle zápisu můžete sčítat obyčejně, jako by to byla přirozená čísla. Třeba čísla 20 a -15 v tomhle formátu budou 10100 a 11110001; když je sečtete, výsledek sice přeteče rozsah charu, ale vyjde vám hodnota 101, což doopravdy znamená číslo 5.

Další důležitý pojem je pořadí bajtů. Když má například int osm bajtů, může nás trápit, jestli jsou (jako v našem lidském zápisu) číslice nejvyššího řádu na začátku, nebo naopak první bajt začíná číslicemi s vahou 1 až 128. Na obvyklých počítačích dnes jednoznačně převažuje ten druhý způsob, kdy první bajt má čísla s nejmenší vahou. Říká se tomu little endian, protože na bližším konci (nevadí, že je to vlastně začátek) jsou menší váhy. Je to užitečné, protože dostatečně malé číslo pak má ve formátech char, short int, int apod. stejný začátek a při troše nadsázky nemusíme opravdový formát takového čísla ani znát. Lidský způsob zápisu čísel bychom naopak označili jako big endian.

Kromě toho někdy chceme pracovat s čísly racionálními. Vezmeme si za vzor vědecký zápis čísel, kdy třeba hmotnost elektronu zapisujeme 9.109·10-31 kg. Hodnota 9109 je mantisa neboli signifikand, hodnota -31 je exponent. Exponent nám vlastně určuje, kam máme v mantise dát desetinnou čárku: kdyby byl nulový, dáme ji za první číslici, jinak ji posuneme o příslušný počet míst směrem doprava (v případě kladného exponentu). Tohle se nazývá zápis s plovoucí řádovou čárkou, anglicky floating point.

Ve dvojkové soustavě použijeme tedy obdobně mocniny dvojky a všimneme si, že první číslice mantisy je vždycky jednička - kdyby ne, posuneme si exponent tak, aby tam jednička byla a o nic nepřijdeme. Protože jsme šetřiví, nebudeme tenhle jeden bit ani ukládat do paměti a jen si ho představíme. Doopravdy to funguje, akorát se pak nedá uložit nula, která opravdu nemá mít jedničku nikde.

Pro uložení nuly si zavedeme speciální hodnoty, a to dokonce tak, že trochu jinou hodnotou označíme +0 a -0. Procesor beztak s tímhle číselným typem počítá pomalu, takže to nevadí (musí dávat pozor, aby platilo +0 = -0). A když už jsme začali, můžeme si rovnou zavést kladné a záponé nekonečno (vychází například z dělení nulou) a speciální hodnotu Not a Number, když třeba odečteme dvě nekonečna od sebe. Může se to zdát jako prasárna, ale takováhle aritmetika občas hodně usnadní práci.

Obyčejný typ float má mantisu tříbajtovou a jednobajtový exponent, častěji se ale používá typ double, který má mantisu o 53 bitech (tedy necelých sedm bajtů) a exponent o 11 bitech.

Používání čísel ve formátu floating point bychom se měli vyhnout, pokud je nepotřebujeme opravdu nutně. Do výpočtů obecně vnášejí nevyzpytatelné zaokrouhlovací chyby; především, nedají se pomocí nich vyjádřit zlomky s něčím jiným než mocninou dvojky ve jmenovateli. Stejně, jako v desítkové soustavě má 1/3 nekonečný desetinný rozvoj, dělá ve dvojkové soustavě takové problémy i 1/5. Když v téměř jakémkoli jazyce sečtete 0.1+0.1+... desetkrát, vyjde vám něco jako 0.999. Třeba v peněžních operacích by to mohlo napáchat velké škody.

Místo toho si můžeme zavést čísla s pevnou desetinnou čárkou (třeba počítat všechno v tisícinách), nebo použít 10 jako základ exponentu, nebo dokonce počítat se zlomky ve formátu čitatel, jmenovatel. Každý způsob má svoje zřejmá omezení - ten poslední naráží snad akorát na problém, že je strašně pomalý. Floating point je naopak bezpečné používat například v grafice a vědeckých výpočtech, kde nás přesnost tolik netrápí. Vůbec naopak není rozumné je používat v cyklech, protože by program kvůli zaokrouhlování nemusel skončit nikdy.