Labor, 12. hét: számábrázolás, operátorok
Pohl László, Czirkos Zoltán · 2019.07.30.
Számábrázolási kérdések és bitműveletek.
Felkészülés a laborra:
- Az operátorokról tanultak átismétlése, a számábrázolásról tanultak megértése.
- A bitműveletek feltétlenül szükségesek a feladatokhoz.
Mit írnak ki az alábbi program egyes sorai? Próbáld meg kitalálni! Futtasd le a programot, és magyarázd meg az eredményt!
A printf() %g formátumsztringje azt jelenti, hogy mindig a legrövidebb kiírási
formát használja – ha a normálalak rövidebb, akkor azt.
#include <stdio.h>
int main(void) {
printf("1. %g\n", 1e40 / 1e-40);
printf("2. %g\n", 1e40f / 1e-40f);
printf("3. %g\n", 1e40);
printf("4. %f\n", 1e40);
printf("5. %s\n", 1e3+1 == 1e3 ? "igaz" : "hamis");
printf("6. %s\n", 1e30+1 == 1e30 ? "igaz" : "hamis");
return 0;
}
Megoldás
Eltérő géptípusok és fordítók esetén az eredmények kicsit mások lehetnek.
1. 1e+80 2. inf 3. 1e+40 4. 10000000000000000303786028427003666890752.000000 5. hamis 6. igaz
A 2-es sorban nem két double, hanem két float értéket osztunk el egymással.
Az eredmény már nagyobb, mint a legnagyobb, float típus által ábrázolható szám, ezért a gép
végtelen nagynak veszi.
A 4-es sorban az látszik, hogy a 1040 értéket a gép nem tudja pontosan ábrázolni.
Az 5-ös és 6-os sor hasonló az előző feladathoz: míg a 103+1 értéke különbözik 103-től, mivel a két szám, 1000 és 1 nem tér el túlzottan egymástól nagyságrendben. A 1030 és 1 esetén ez már nem igaz.
Írj programot, amely kiszámítja az alábbi tört valós (tizedestört) értékét! (−0.653333-et kell kapj).
4
4 + 2 − (3 − (6 + ─))
5
─────────────────────
3(2−7)
Megoldás
Valahol, leginkább a 4/5 törtnél, szerepelnie kell a kifejezésben egy valós számnak. Különben egész osztás történik. Ezen kívül, figyelni kell a zárójelezésre; zárójelbe kell tenni a számlálót is, és a nevezőt is, különben nem az összeget osztjuk, vagy nem a szorzattal osztunk, az operátorok precedenciája és asszociativitása miatt.
#include <stdio.h>
int main(void) {
printf("%f", (4+2-(3-(6+4.0/5)))/(3*(2-7)));
return 0;
}
Az alábbi program egy olyan ciklust tartalmaz, mely addig fut, amíg egy minden iterációban megnövelt szám pozitív marad.
Matematikailag ez nyilvánvalóan végtelen ciklus. No de mi a helyzet a gyakorlatban? Próbáld ki int és short
int, signed char típussal is! Magyarázd meg a jelenséget! Miért pont azok a számok jelentek meg, amiket
látsz?
#include <stdio.h>
int main(void) {
int i = 1;
while (i > 0) {
++i;
}
printf("%d\n", i);
return 0;
}
Írj programot, amelyik kiszámítja a 2 első 32 hatványát, a 20-tól indulva!
Használd ehhez a léptető << operátort! Mit tapasztalsz, ki tudod írni
a 2 első 40 hatványát is?
Megoldás
Figyelned kell arra, hogy 231 már nem fér el egy 32 bites
signed int típusban. Ezért az 1u értéket kell léptetni,
és a printf() számára is jelezni kell, hogy előjel nélküli számról
van szó: %u. Ha nem így csináltad, ezt észre fogod venni, amikor megjelenik
egy negatív szám a kimeneten.
#include <stdio.h>
int main(void) {
for (int x = 0; x < 32; ++x)
printf("%d: %u\n", x, 1u << x);
return 0;
}
Ezek után módosítsd úgy a programodat, hogy egy unsigned int típusú
változó értékét változtasd benne, mindig eggyel balra léptetve a <<=
operátorral! Az előző feladat tapasztalatából kiindulva, a ciklust futtathatod addig, amíg
a változó túl nem csordul – nem fixen 32 iterációval.
Megoldás
#include <stdio.h>
int main(void) {
unsigned int x = 1;
while (x > 0) {
printf("%u\n", x);
x <<= 1;
}
return 0;
}
Ezek alapján olyan programot is lehetne írni, amelyik egy adott típusról megmondja, hogy
hány bites: addig kell léptetni, amíg túl nem csordult. Módosítsd úgy a programot, hogy
képes legyen megszámolni egy előjel nélküli egész változó bitjeinek számát, más néven
szóhosszúságát! Mekkora egy unsigned char, egy unsigned short int,
egy unsigned int, egy unsigned long int, és egy unsigned long long int
a gépen, amelyiken dolgozol? Figyelj a fordító hibaüzeneteire, a printf() paraméterezését
módosítani kell majd az egyes típusoknál!
Megoldás
#include <stdio.h>
int main(void) {
int db = 0;
unsigned long long int x = 1;
while (x > 0) {
printf("%llu\n", x);
x <<= 1;
++db;
}
printf("Az unsigned long long int %d bites.\n", db);
return 0;
}
Dolgozz tovább abból a feltételezésből kiindulva, hogy a gépeden az unsigned long int típus
32 bites. Írj egy olyan programrészletet, amelyik egy ilyen típusú változóban tárolt szám bitjeit
kiírja! Pl. 29 esetén ez a bitminta 00000000000000000000000000011101,
mert
29 = 1×24+1×23+1×22+0×21+1×20,
azaz 2910 = 111012.
Tipp
Emlékezz vissza, a >> operátorral tudsz jobbra léptetni egy számot. Ha kíváncsi vagy valamelyik
bitjére, akkor annyiszor lépteted jobbra, ahányadik bit az érdekes – utána pedig a bitenkénti ÉS operátorral kivágható a legalsó bit.
Megoldás
Az alábbi mintamegoldásban a jobbra léptetés >> után az ÉS operátorral & kivágjuk
a legalsó bitet. Mivel így az eredmény vagy 0 lesz, vagy 1 (tehát vagy csak a legalsó biten lesz egy 1-es, vagy még ott sem),
ez rögtön kiíratható.
#include <stdio.h>
int main(void) {
unsigned long int value = 29;
for (int i = 31; i >= 0; --i)
printf("%lu", value>>i & 1);
return 0;
}
Az előző feladat folytatása: a bitenként kiíró programodat írd át úgy, hogy az 1-esek helyére
'#' karaktert, a 0-k helyére ' ' (szóköz) karaktert tegyen!
(Ehhez használhatod a ?: operátort is.)
Utána pedig úgy, hogy ne csak egy szám bitmintáját írja ki ilyen formában, hanem az alábbi tömb összes számát, egymás alá! Mit látsz?
unsigned long szamok[9] = { 0U, 1940142534U, 2216969513U, 2216969513U,
1681671631U, 337925385U, 3819190537U, 1024U, 0U };
Megoldás
#include <stdio.h>
int main(void) {
unsigned long szamok[9] = { 0U, 1940142534U, 2216969513U, 2216969513U,
1681671631U, 337925385U, 3819190537U, 1024U, 0U };
/* az összes szám */
for (int szam = 0; szam < 9; ++szam) {
/* az egyik szám bitmintája */
for (int i = 31; i >= 0; --i)
printf("%c", szamok[szam]>>i & 1 ? '#' : ' ');
printf("\n");
}
return 0;
}
Ha megvan, amit látni kell, akkor próbáld ki, hogy a kirajzolás előtt az összes tömbelemet megváltoztatod az alábbi módokon:
tömbelem = tömbelem & 65535tömbelem = tömbelem & ~65535tömbelem = tömbelem | 65535tömbelem = tömbelem | ~65535tömbelem = tömbelem ^ 65535tömbelem = tömbelem ^ ~65535
Magyarázd meg a kapott eredményeket! (Segít, ha megvizsgálod a 65535 és a ~65535 bitmintáját is előbb.)
Megoldás
65535 bitmintája 00000000000000001111111111111111, 16 darab 0 és 16 darab 1
bit. ~65535 ugyanez, csak bitenként negálva, tehát 11111111111111110000000000000000. Ha ezt a két
értéket bitenkénti ÉS kapcsolatba hozzuk a tömb számaival, akkor vagy csak a „QPA”, vagy csak az „SCH” maradnak meg, mivel
X ÉS 0 = 0, illetve X ÉS 1 = X (lásd az igazságtáblázatot). A bitenkénti VAGY kapcsolattal 1-be
billennek a bitek. A bitenkénti KIZÁRÓ VAGY (XOR) kapcsolattal pedig negálni lehet a kiválasztott biteket. Így vagy az „SCH”,
vagy a „QPA” felirat lesz negálva.
Az előző feladat tömbje egy fekete-fehér rajzot tárolt a számokban. Ebben a feladatban ezt kell továbbfejlesztened.
Hozz létre egy 32×24 pontból álló rajzot, és dolgozz azon! A rajz szélessége (32) az unsigned long int
méretéből adódik; a magasság (24) a tömbméretből. A konzolablak 24 vagy 25 sor magas szokott lenni, így el fog férni a rajzod.
Írj programrészt, amelyik:
- Letörli a rajzot, azaz mindegyik bitet „feketévé”, 0-vá változtatja!
- Rajzol egy pontot, azaz „fehér”, 1-es bitet tesz egy adott (x, y) pozícióra!
Készíts a fentiek használatával egy rajzot! Rajzolj pl. kört vagy téglalapot, vagy egyéb formát!
Megoldás
Az alábbi program egy behajtani tilos táblát rajzol:
#include <stdio.h>
#include <math.h>
int main(void) {
unsigned long int rajz[24];
/* törlés */
for (int y = 0; y < 24; ++y)
rajz[y] = 0;
/* kör */
double r = 9.5;
for (double alfa = 0; alfa < 360; alfa += 3) {
x = 16 + r*cos(alfa);
y = 12 + r*sin(alfa);
rajz[y] |= 1 << (31-x); /* 1-be állítás */
}
/* téglalap */
int x1 = 9, y1 = 10;
int x2 = 22, y2 = 14;
for (int y = y1; y <= y2; ++y) {
rajz[y] |= 1 << (31-x1);
rajz[y] |= 1 << (31-x2);
}
for (int x = x1; x <= x2; ++x) {
rajz[y1] |= 1 << (31-x);
rajz[y2] |= 1 << (31-x);
}
/* kirajzolás */
for (int y = 0; y < 24; ++y) {
/* az egyik szám bitmintája */
for (int x = 31; x >= 0; --x) {
printf("%c", rajz[y]>>x & 1 ? '#' : '.');
}
printf("\n");
}
return 0;
}