Převod čísla v pohyblivé řádové čárce
Na základě přednášek a cvičení z BPC1A napište program sledující algoritmus pro převod float čísla (do formátu IEEE 754) tak aby ve výsledku vytiskl číslo v dané přesnosti (single, double, extended) v binárním a hexadecimálním tvaru.
Postup:
Ve funkci main nadefinujte proměnné (vhodného typu), do kterých uložíte reálné číslo pro převod, výsledky a mezivýsledky.
Zjistěte hodnotu exponentu a mantisy. (zjistěte hodnoty celé a desetinné části, z nich určete znaménko, binární reprezentaci mantisy a hodnotu exponentu)
Na základě zvoleného typu převodu (single, double, extended) vytvořte z již vypočtených hodnot příslušné sekce výsledku „znaménko/exponent/mantisa“ a vytvořte výsledek v odpovídajícím tvaru.
Výsledek vytiskněte.
Nástin řešení:
0) Řešení není tak jednoduché jako „na papíru“. Musíme uvažovat HW vlastnosti platformy a HW závislosti jazyka C. Možných řešení je celá řada. Stejně tak nástrojů, které můžeme použít. Následující berte jako jednu z možností.
1) Jelikož chceme pracovat s bity, musíme reálné číslo „převést“ do celočíselné proměnné. Musíme tedy zvolit vhodný datový typ pro převod. Nejdříve si zjistíme velikost datových typů. Nejlepší situace je, pokud reálný i celočíselný datový typ mají stejný rozměr. Alternativně můžeme použít celočíselný datový typ, který zabírá více bytů (s tím potom souvisí složitější algoritmy - pokud chceme používat algoritmus jako pro stejně velký datový typ, musíme na úvod upravit proměnnou přesunutím dat na správné místo (použijeme-li ukazatel, potom jsou data v "horní části" většího datového typu. Při práci s bity pro stejný datový typ budeme ale (nejčastěji) pracovat od "dolních bitů")).
Ke zjistění (bytové) vellikosti datových typů slouží klíčové slovo (operátor) sizeof.
Na různých platformách mohou mít stejné datové typy jazyka C/C++ různé rozměry (budou zabírat jinou velikost paměti). Norma udává pouze číslo, které se do typu musí určitě vejít (a ve skutečnosti tedy může být takový nebo větší). Například u typu long long se udává, že musí pojmout 64 bitové číslo, pokud má např. 128 bitů (tj >64b) , je podmínka splněna.
V knihovně stdint.h jsou i "pevné" datové typy. Tj.
datové typy, které mají danou bitovou velikost (exact níže). Datové
typy jsou zde vlastně tři -
"exact", které mají přesnou
velikost (u)intN_t
"least", které mají tolik nebo více
bitů (u)int_leastN_t
"fast", které volí (z least)
takový, pro který probíhají nejrychleji výpočty (tj. "sedí"
buď procesoru, nebo algoritmu (pro "velice dlouhé" typy)).
Pro řešení na jedné platformě by se tedy hodila varianta "exact" (což je ekvivalentní výběru typu, který je stejně velký - nepočítáme-li s přechodem na jinou platformu).
Alternativou k jednomu datovému typu je použití unionu (s "překrývajícími" se datovými typy) nebo pole charů, případně bitové pole (ve struktuře). Kdy se typy "překrývají" (realizace může být závislá na překladači) a tedy se na double můžeme "dívat" pomocí char, int, long ...
2) Druhá věc, kterou musíme v přípravě ověřit a případně ošetřit
je endianita. Platí jak pro "samostatné" proměnné, tak pro
řešení s unionem.
Představme si dvoubytovou celočíselnou proměnnou
(int či short, v závislosti na platformě). Přiřadíme-li proměnné
hodnotu 0x11FF, potom v paměti může být uložena buď v pořadí 0x11,
0xFF nebo 0xFF, 0x11. Toto je nutné uvažovat při řešení a v případě,
že přistupujeme k proměnným ve fyzické paměti, musíme toto zohlednit.
Pozn.: v případě, že načítáme proměnné jako hodnoty, je endianita
(většinou) stejná a tedy dojde pro stejně dlouhé datové typy
(většinou) ke stejnému „přeskládání“ při čtení z paměti
do procesoru. Proto dáváme přednost stejně dlouhému celočíselnému
typy jako je převáděný reálný.
Test endianity můžeme provést například tak, že se podíváme na to, kde v paměti je první bit (zleva) reálného čísla. Co je v tomto bitu?
Jelikož v tomto bitu je znaménko, může test vypadat například
(double = 64 bitů, tj 8bytů, tj sizeof(double)=8):
double
val = 1; // proměnná, na které provádím testy
unsigned char *pom;
// proměnná, díky které „vidím“ pamět jako po sobě jdoucí
byty
// (neprojeví se endianita) a můžu ji snadno zobrazit bitově
(=hexa).
pom = (unsigned char*) &val; // proměnnou char
„namířím“ na začátek testované proměnné
double
for(i=0;i<8;++i) printf(“%x“, pom[i]; //
tisk v hexa (tisk jako int, mohou se objevit
// přebytečné nuly,
které v char nejsou)
val = -1 * val; // změním
znaméno
for(i=0;i<8;++i) printf(“%x“, pom[i]; //
tisk v hexa – jediný rozdíl by měl být na pozici znaménka
//
z místa změny lze odvodit endianitu
3) Vlastní tisk po bitech můžu uskutečnit pomocí převodu double na
celočíselnou proměnnou a v ní přistupovat k bitům.
Na reálnou
proměnnou si ukážu ukazatelem celočíselného bezznaménkového typu
stejné délky. Pomocí tohoto ukazatele načtu hodnotu do proměnné
stejného typu jako ukazatel. Jelikož tato proměnná je celočíselná,
můžu s ním bitově pracovat.
Nejprve provedu přeskládání bytů
spůsobené endianitou – je-li to potřeba.
Následně (pro
double 64 bitů):
Znaménko zjistím buť pomocí masky s bitem na
poslední pozici (nevíce vlevo) (~((~(0ull))>>1)) tj 0x80 00 00
00 00 00 00 00. Nebo provedu posun o 63 bitů doprava a tím mi
v
proměnné zústane jediný bit.
Obdobně je možné získat exponent:
maska+posun, nebo posun a maska. Převod z proměnné ullval je: ullval
& 0x7F FF 00 00 00 00 00 00 a následně posun doprava o 6*8 bitů.
Nebo posun o 6*8 bitů a maska 0x7FFF. Pro získání „skutečné“
hodnoty exponentu je nutné si uvědomit, že zatím máme hodnotu „s
posunutou nulou“ a tedy pro skutečný exponent ji musíme
upravit.
Vlastní hodnotu mantisy získáme opět maskováním : ullval
& 0xFF FF FF FF FF FF. Tím jsme dostali bitový obraz uložené
mantisy. V případě, že nás zajímá její hodnota je nutná další úprava.
Musíme si uvědomit, že (kromě extended) reálné formáty „zahazují“
úvodní jedničku v bitové reprezentaci – a proto ji musíme
přidat (například maskováním s jedničkou na dané pozici pomoci
operace OR = |. Následně si musíme uvědomit, že se vlastně jedná o
desetinné číslo ve tvaru 1.xxxx – takže opět je nutná
úprava.
Pozn.: neřešili jsme speciální případy jako je Inf a
NaN.
Pozn.: při řešení podobných problémů by se mohly hodit
funkce: htonl, ntohl ...
Vyzkoušejte převod z řetězce na reálné číslo v double formátu – stejným postupem jako „na papíru“.
Poslední změna 2017-03-02