Přednáška http://efis.tul.cz/~dana.nejedlova/ Počítače I Přednáška - Čísla v počítači Cisla_v_pocitaci.pptx Probralo se to do snímku "Unicode". Ke zkoušce je třeba znát: - Jaká část formátu IEEE 754 určuje - rozsah čísla a - Exponent - přesnost čísla? - Mantisa - Rozpoznat, zda je dané reálné číslo v desítkové soustavě reprezentovatelné v počítači ve formátu IEEE 754 bez ztráty přesnosti. - Jak se liší rozdíl mezi sousedními možnými hodnotami čísla ve formátu IEEE 754 od čísla ve formátu dvojkového doplňku? - Ve formátu IEEE 754 může být různý. Ve formátu dvojkového doplňku se sousední čísla liší o jednu. Cvičení Přetečení a podtečení #include #include #define MAX 1000 int main() { char c; for (c = 0; c < MAX; c++) { printf("%u ", c); } return 0; } Tento program se zacyklí, protože proměnná c nemůže být rovna nebo vyšší než 1000. Proměnná c je typu char a datový typ char je vždy jednobajtový, má tedy 8 bitů. Hodnota uložená v 8 bitech může mít hodnotu od 0 do 11111111 (binárně) = 255. #include #define MAX 1000 int main() { int i; unsigned char c; for (i = 0; i < MAX; i++) { printf("%u ", c); c++; /*c = c + 1;*/ } return 0; } Tento program se překládá s varováním \main.c|6|warning: 'c' is used uninitialized in this function [-Wuninitialized]| Proměnnou c musíme inicializovat, protože k její původní hodnotě něco přičítáme příkazem c++. #include #define MAX 1000 int main() { int i; unsigned char c = 0; /* deklarace s inicializaci */ for (i = 0; i < MAX; i++) { printf("%u ", c); /* Ridici retezec formatu "%u " vypisuje hodnoty dle matematickych pravidel prevodu z binarni do desitkove soustavy, tedy jen kladna cisla. */ c++; } return 0; } Tento program vypisuje cyklickou řadu od 0 do 255. Při přičtení čísla 1 k číslu 255 dojde k přetečení a výsledek se vypíše jako 0, viz snímek "Rozsah dvojkového doplňku v 8 bitech" mé přednášky pod odkazem "Přednáška - Čísla v počítači" na stránce http://efis.tul.cz/~dana.nejedlova/ #include #define MAX 1000 int main() { int i; char c = 0; for (i = 0; i < MAX; i++) { printf("%u ", c); c++; } return 0; } Tento program se od toho předchozího liší tím, že proměnná c je typu char a ne unsigned char. Řídící řetězec formátu "%u " potom hodnotu proměnné c vypisuje po přetečení jako 4294967168. K přetečení dojde po přičtení čísla 1 k číslu 127. Datový typ char bez modifikátoru unsigned je totiž znaménkový, a proto k přetečení dojde už po hodnotě 127 a ne až po hodnotě 255. Tento znaménkový datový typ zde vypisujeme pomocí řídícího řetězce formátu "%u ", který je pro neznaménkové datové typy. Hodnota 4294967168 vznikla tak, že se k řádově nejnižším bitům 10000000 připojily řádově vyšší 3 bajty se samými jedničkami, což je možné vidět windowsovské kalkulačce v režimu Programátorská. Toto chování je pravděpodobně závislé na platformě, viz https://stackoverflow.com/questions/51047929/assigning-128-to-char-variable-in-c/51048273 #include #define MAX 1000 int main() { int i; char c = 0; for (i = 0; i < MAX; i++) { printf("%d ", c); /* Ridici retezec formatu "%d " vypisuje hodnoty dle pravidel pro dvojkový doplňek, tedy znaménková cisla. */ c++; } return 0; } Tento program vypisuje cyklickou řadu od -128 do 127. Při přičtení čísla 1 k číslu 127 dojde k přetečení a výsledek se vypíše jako -128. #include #define MAX 1000 int main() { int i; char c = 0; for (i = 0; i < MAX; i++) { printf("%d ", c); c--; /*c = c - 1;*/ } return 0; } Tento program odečítá jedničku od proměnné c. Při odečtení čísla 1 od čísla -128 dojde k podtečení a výsledek se vypíše jako 127. #include #define MAX 1000 int main() { int i; unsigned char c = 0; for (i = 0; i < MAX; i++) { printf("%c ", c); /* Ridici retezec formatu "%c " vypisuje znaky. */ c++; } return 0; } Tento program místo čísel vypisuje cyklicky znaky dle ASCII tabulky, viz https://en.wikipedia.org/wiki/ASCII Program čtyřikrát pípne, protože jeden ze znaků je Bell, viz https://en.wikipedia.org/wiki/Bell_character #include #define MAX 300 int main() { int i; unsigned char c = 0; for (i = 0; i < MAX; i++) { printf("%d-%c ", c, c); c++; /*c = c - 1;*/ } return 0; } Tento program vypisuje cyklicky kód znaku, znak "-" a znak. #include #define MAX 300 int main() { int i; unsigned char c = 0; for (i = 0; i < MAX; i++) { printf("%o-%d-%x-%c\n", c, c, c, c); c++; } return 0; } Tento program vypisuje cyklicky kód znaku v osmičkové soustavě, znak "-", kód znaku v desítkové soustavě, znak "-", kód znaku v šestnáctkové soustavě, znak "-" a znak. Za každým znakem je konec řádku a, protože se do konzole vejde jen omezený počet řádků, tak se na ní nezobrazuje řada od kódu 0 ale od kódu 6. Zatímco v ASCII tabulce jsou jen sedmibitové znaky, tento program vyisuje i znaky, které mají řádově nejvyšší osmý bit rovný jedné. Zaokrouhlovací chyby reálných čísel Kvadratická rovnice https://cs.wikipedia.org/wiki/Kvadratick%C3%A1_rovnice Rovnice (x-0.1)*(x-0.1)=0 má jediné řešení x=0.1. Pokud je ve tvaru axx + bx + c = 0, tak a = 1, b = -0.2, c = 0.01 a použijeme vzorec (-b+-sqrt(b*b-4*a*c))/(2*a) tak diskriminant = b*b-4*a*c testujeme na rovnost nule, ale nemusí nám nula vyjít přesně. Takže test "if (diskriminant == 0)" nepracuje správně. #include int main() { float a = 1, b = -0.2, c = 0.01, diskriminant; diskriminant = b * b - 4 * a * c; if (diskriminant == 0) { printf("jeden koren\n"); } else { printf("dva koreny\n"); } printf("Diskriminant = %.*f\n", 20, diskriminant); return 0; } Tento program vypíše "dva koreny" a hodnotu diskriminantu na 20 desetinných míst, kde je vidět, že to není přesná nula. Definujeme proto konstantu PRESNOST = 0.0001 a testujeme "if (fabs(d) < PRESNOST)". #include #include /* kvuli funkci fabs() */ #define PRESNOST 0.000001 int main() { float a = 1, b = -0.2, c = 0.01, diskriminant; diskriminant = b * b - 4 * a * c; if (fabs(diskriminant) < PRESNOST) { printf("jeden koren\n"); } else { printf("dva koreny\n"); } printf("Diskriminant = %.*f\n", 20, diskriminant); return 0; } Tento program správně vypíše "jeden koren". Kdybychom řešili kvadratickou rovnici (x-0.1)*(x-0.100000000001)=0 ve tvaru axx + bx + c = 0, tak a = 1, b = -0.200000000001, c = 0.0100000000001, #include #include /* kvuli funkci fabs() */ #define PRESNOST 0.000001 int main() { float a = 1, b = -0.200000000001, c = 0.0100000000001, diskriminant; diskriminant = b * b - 4 * a * c; if (fabs(diskriminant) < PRESNOST) { printf("jeden koren\n"); } else { printf("dva koreny\n"); } printf("Diskriminant = %.*f\n", 20, diskriminant); return 0; } tak tento program vypíše nesprávně "jeden koren", ale nedá se to řešit zvýšením přesnosti například #define PRESNOST 0.00000000000000001 Potom sice pro tuto úlohu program vypíše správně "dva koreny", ale pro předchozí rovnici, kde a = 1, b = -0.2, c = 0.01, vypíše také "dva koreny", kde už to bude nesprávně. Konstanta PRESNOST pro datový typ float (jednoduchá přesnost) nesmí být nižší než 1e-6 a pro datový typ double (dvojnásobná přesnost) 1e-15, viz kapitola "1.3.1 Poučení" mé přednášky pod odkazem "Přednášky" na stránce http://efis.tul.cz/~dana.nejedlova/ #include #include #define PRESNOST 1e-10 /* 0.0000000001 */ #define POCET_DES_MIST 20 int main() { double a = 1, b = -0.2, c = 0.01, diskriminant; diskriminant = b * b - 4 * a * c; if (fabs(diskriminant) < PRESNOST) { printf("jeden koren\n"); } else { printf("dva koreny\n"); } printf("Diskriminant = %.*f\n", POCET_DES_MIST, fabs(diskriminant)); printf("PRESNOST = %.*f\n", POCET_DES_MIST, PRESNOST); return 0; } Tento program ukazuje, že pro datový typ double se diskriminant spočítá přesněji než pro datový typ float. Kompletní pravidla pro výpis dat funkcí printf() jsou na stránce http://www.cplusplus.com/reference/cstdio/printf/