Labor, 4. hét: számábrázolás
Pohl László, Czirkos Zoltán · 2024.09.20.
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>
#include <math.h>
int main(void) {
printf("1. %f\n", 1.23456789123456789123456789e40);
printf("2. %g\n", 1e40);
printf("3. %s\n", 1e3+1 == 1e3 ? "igaz" : "hamis");
printf("4. %s\n", 1e30+1 == 1e30 ? "igaz" : "hamis");
printf("5. %g\n", pow(10, 40) / pow(10, -40));
printf("6. %g\n", powf(10, 40) / powf(10, -40));
return 0;
}
Megoldás
Eltérő géptípusok és fordítók esetén az eredmények kicsit mások lehetnek.
1. 12345678912345679949757438004016390864896.000000 2. 1e+40 3. hamis 4. igaz 5. 1e+80 6. inf
Az 1-es sorban az látszik, hogy a kiírt 1040 nagyságrendű értéket a gép nem tudja pontosan ábrázolni.
A 2-esben megadott %g
formátum a rövidsége miatt preferálja a normálalakot, de ez
ne tévesszen meg senkit: a szám valójában itt sem pontos, csak kevés a megjelenített tizedesjegyek száma.
Az 3-as és 4-es 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.
Az 5-ös sorban két double
értéket, a 6-osban két float
-ot 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.
Í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 int x = 1;
while (x > 0) {
printf("%lu\n", x);
x <<= 1;
++db;
}
printf("Az unsigned 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[8] = { 0U, 1931988508U, 581177634U, 581374240U,
581177632U, 581177634U, 1919159836U, 0U };
Megoldás
#include <stdio.h>
int main(void) {
unsigned long szamok[8] = { 0U, 1931988508U, 581177634U, 581374240U,
581177632U, 581177634U, 1919159836U, 0U };
/* az összes szám */
for (int y = 0; y < 8; ++y) {
/* az egyik szám bitmintája */
for (int x = 31; x >= 0; --x)
printf("%c", szamok[y]>>x & 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 & 65535
tömbelem = tömbelem & ~65535
tömbelem = tömbelem | 65535
tömbelem = tömbelem | ~65535
tömbelem = tömbelem ^ 65535
tö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 a kép ba vagy jobb oldala marad 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.
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) {
int x = 16 + r*cos(alfa*3.14/180);
int y = 12 + r*sin(alfa*3.14/180);
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;
}