====== 2. Reprezentace dat v paměti a plovoucí řádová čárka ====== * pro vyučující: [[..:..:internal:tutorials:02:start|cvičení 2]] ===== Osnova cvičení ===== - základní reprezentace čísel - plovoucí řádová čárka - sestavovací program make - Upozornění na 1. domácí úkol ([[https://dcenet.felk.cvut.cz/apo/]]) ===== Co bych si měl na druhé cvičení zopakovat / připravit ===== - operace sčítání, odčítání, násobení a dělení - logické operace s binárními čísly (and, or, rotace, ...) - přečíst/zopakovat znalosti z {{ ..:..:lectures:apolos_v11.pdf|APOLOS}} - reprezentace reálných čísel ([[https://en.wikipedia.org/wiki/IEEE_754|IEEE 754]]) ===== Náplň cvičení ===== ==== Reprezentace čísel v paměti ==== Na cvičení budeme vycházet z následujícího programu v C, který budeme dále modifikovat. Program pro zobrazování reprezentace čísel v paměti: /* Simple program to examine how are different data types encoded in memory */ #include /* * The macro determines size of given variable and then * prints individual bytes of the value representation */ #define PRINT_MEM(a) print_mem((unsigned char*)&(a), sizeof(a)) void print_mem(unsigned char *ptr, int size) { int i; printf("address = 0x%016lx\n", (long unsigned int)ptr); for (i = 0; i < size; i++) { printf("0x%02x ", *(ptr+i)); // == printf("0x%02x ", ptr[i]); } printf("\n"); } int main() { /* try for more types: long, float, double, pointer */ unsigned int unsig = 5; int sig = -5; /* Read GNU C Library manual for conversion syntax for other types */ /* https://www.gnu.org/software/libc/manual/html_node/Formatted-Output.html */ printf("value = %d\n", unsig); PRINT_MEM(unsig); printf("\nvalue = %d\n", sig); PRINT_MEM(sig); return 0; } Zdojový kód programu je z počítačů v laboratoři možné zkopírovat z adresáře ''/opt/apo/binrep/print_binrep''. K překladu programu použijte buď přímo příkaz: gcc -Wall -pedantic -o print_binrep ./print_binrep.c nebo přiložený soubor s popisem sestavení (''Makefile'') pro program 'make'. K editaci použijte některý z nainstalovaných editorů (geany, vim, emacs, qtcreator, ...). Pro ty, co nemají vlastní preferenci je vhodné začít třeba s programem [[https://www.geany.org/|geany]]. Alternativa v jazyce Python #!/usr/bin/python3 import struct a = 1234.56789 b = -11.1111111111111111111111 c = a + b buf = struct.pack('f', c) print ('C float BE:' + ' '.join(["{0:02x}".format(b) for b in buf])) print ('C float BE:' + str(struct.unpack('>f', buf)[0])) buf = struct.pack('d', c) print ('C double BE:' + ' '.join(["{0:02x}".format(b) for b in buf])) print ('C double BE:' + str(struct.unpack('>d', buf)[0])) Více o ukládání proměnných v specifikovaném formátu datové reprezentace v moduly jazyka Python [[https://docs.python.org/3/library/struct.html|struct — Interpret bytes as packed binary data]]. ==== Sestavovací program make ==== V situaci, kdy máme jeden zdrojový soubor není problém spouštět překladač pokaždé ručně. U větších projektů ale máme souborů víc a často různých typů – máme třeba zdrojové kódy v C a v assembleru, dokumentaci v XML a třeba i něco dalšího. Překládat ručně každý soubor zvlášť a ještě k tomu jiným překladačem a spojovat výsledek dohromady je zbytečně velká práce. Proto byl vymyšlen program [[https://www.gnu.org/software/make/manual/html_node/index.html|make]]. Jeho základní myšlenka je ta, že se popíše jak se překládá který typ souboru a stanoví se které soubory jsou potřeba pro "vyrobení" jiného souboru. Program make funguje tak, že po spuštění si přečte soubor Makefile, kde jsou zapsána pravidla pro kompilaci a vyvolává postupně překladače tak, jak je potřeba. V souboru Makefile existují čtyři typy řádek: * popis závislostí, * příkazy pro kompilaci, * nastavování proměnných, * komentáře. Nejjednodušší Makefile pro kompilaci programu hello vypadá následovně. hello: hello.c Obsahuje pouze jednu řádku s popisem závislostí. Takto zapsaná řádka říká, že pro vytvoření souboru hello je potřeba soubor hello.c. Tomu, co je vlevo od dvojtečky se říká target (cíl) a vše co je napravo jsou takzvané prerekvizity. Program make má v sobě vestavěna implicitní pravidla, díky nimž ví, jak má provést překlad. Kdyby tato pravidla neexistovala, musel by náš Makefile vypadat takto: hello: hello.c gcc -Wall -o hello hello.c Na druhé řádce, která začíná tabulátorem, je příkaz, který bude vykonán pokud make usoudí, že je potřeba překompilovat soubor hello. Implicitní pravidla říkají, jak z jednoho typu souboru vyrobit jiný typ souboru. Tato pravidla jsou většinou definována pomocí proměnných. Chceme-li implicitní pravidlo jen trochu pozměnit, nemusíme ho definovat znova, ale stačí změnit jen hodnoty proměnných, které se v pravidlech používají. CFLAGS = -g -Wall CC = m68k-elf-gcc hello: hello.c Pomocí proměnné CFLAGS se například určuje, s jakými přepínači má být spouštěn překladač jazyka C. Jak se jmenuje překladač se zase specifikuje v proměnné CC[3]. Pokud chceme překládat větší projekt, bývá zvykem rozšířit Makefile o další targety: all: hello clean: rm -f hello hello: hello.c gcc -Wall -g -o hello hello.c O programu make by toho šlo napsat mnohem víc. Jdou pomocí něj překládat i velmi komplikované projekty jako například samotné jádro Linuxu. Dost často ale není potřeba vytvářet složité soubory Makefile ručně. Nástroje jako autoconf či nějaké IDE většinou Makefile generují automaticky. ==== Úkoly ==== - přeložte a spusťte uvedený program, interpretujte výstupy programu a postupně ho modifikujte tak * aby tiskl vnitřní reprezentaci i jiných datových typů (např. char, float, long, int*) * aby vytiskl tabulku celých čísel včetně jejich reprezentace v rozsahu -16 až 15 * aby realizoval operace sčítání a odčítání dvou proměnných (celá čísla) a vytiskl na obrazovku vstupní operandy a výsledky těchto operací včetně jejich vnitřní reprezentace * vyzkoušejte operace s kladnými i zápornými čísly, zaměřte se i na takové hodnoty, kdy po provedení operace dojde k přetečení - Vyzkoušejte si ručně sčítání a odčítání celých čísel v doplňkovém kódu * demonstrujte výpočet např. 7+6, 7-6 * procvičte výpočet na dalších číslech a s pomocí programu výsledky ověřte * Kdy může dojít k přetečení a jak poznáte, že k němu došlo? - Násobení celých čísel * demonstrujte výpočet např. 7*6 * jak se výpočet změní v případě záporných čísel? např. -7*6, -7*(-6), 7*(-6) * rychlá verze hw násobičky (jak zrychlit opakované sčítání na jedné sčítačce použitím většího množství sčítaček?) - Dělení celých čísel * demonstrujte výpočet např. 42/7, 43/7 * jak se výpočet změní v případě záporných čísel? - Reprezentace reálných čísel (IEEE 754) * binární reprezentace reálných čísel (float - 32bit, double - 64bit) * převeďte na binární reprezentaci číslo -0.75, ověřte správnost s pomocí programu pro zobrazení reprezentace v paměti * převeďte float z binární reprezentace 0xC0A00000 na reálné číslo v desitkové soustavě * demonstrujte výpočet (v desítkové soustavě) 9.999*10^1 + 1.1610*10^(-1), předpokládejte, že je možné uložit pouze 4 cifry čísla a 2 cifry exponentu. * Návod: 1) zarovnání čísel, 2) součet, 3) normalizace, 4) zaokrouhlení * v binární reprezentaci sečtěte čísla 0.5 a -0.4375 * demonstrujte výpočet (v desítkové soustavě) 1.110*10^10 * 9.200*10^(-5) * v binární reprezentaci vynásobte čísla 0.5 a -0.4375 - Se cvičícím se pokuste program přepsat bez použití makrodefinice * získejte adresu proměnné v paměti, například unsigned char *p = (unsigned char *)&unsig * počet buněk, které proměnná zabírá - sizeof(unsig) * navrhněte algoritmus pro zobrazení v binární soustavě ===== Užitečné odkazy ===== * [[http://support.dce.felk.cvut.cz/pos/cv-langc/|http://support.dce.felk.cvut.cz/pos/cv-langc/]] - Základy jazyka C * [[http://www.gnu.org/software/libc/manual/html_node/Formatted-Output.html|http://www.gnu.org/software/libc/manual/html_node/Formatted-Output.html]] - Dokumentace k řízení formátovaného výstupu pro implementaci **printf** z knihovny funkcí pro jazyk C ([[http://www.gnu.org/software/libc/|GLIBC]]) z projektu [[http://www.gnu.org/|GNU]] * [[https://bootlin.com/doc/legacy/command-line/command_memento.pdf|Základní příkazy pro práci v příkazové řádce]] na [[https://bootlin.com/|Bootlin]] * Materiál k IEEE 754: {{..:apo2.pdf|}}, nová česká verze APO2020_cv2 {{ :courses:b35apo:tutorials:02:apo2020_cv2.pdf | PDF }} a {{ :courses:b35apo:tutorials:02:apo2020_cv2.pptx | PPTX}} * Popis formátu IEEE 754 na [[https://en.wikipedia.org/wiki/IEEE_754|Wikipedii]] * [[https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libgcc/fp-bit.c]] - implementace operací v plovoucí řádové čárce s využitím operací v pevné řádové čárce tak v knihovně kompilátoru [[http://gcc.gnu.org|GCC]] pro procesory, které hardwarovou implementací operací nedisponují. ===== Domácí úkoly ===== * domácí úkoly 1 až 4 budou zadané a odevzdávané elektronickou formou * vstup k zadání a odevzdání úkolů je přes adresu [[https://dcenet.felk.cvut.cz/apo/|https://dcenet.felk.cvut.cz/apo/]] * Na stránce "Assignments" naleznete seznam zadaných úkolů * Pro nácvik práce s odevzdávacím systémem je k dispozici nehodnocená varianta prvního úkolu **1st training homework**, kde máte 999 pokusů. Nazapočítávají se z ní body, ale i tak ji musíte vyřešit aspoň na polovinu, tedy na 3 body, abyste směli odevzdat 1. hodnocený domácí úkol. * Případnými problémy s odevzdávacím systémem se obracejte na svého cvičícího nebo přímo na autora/správce systému [[courses:b35apo:teacher:susta:start|Richarda Šustu]]