12. diel - Assembler - Signed a Unsigned čísla
V minulej lekcii, Assembler - Registre procesora , sme si ukázali skoro všetky registre procesorov x86 a x64.
V ASM tutoriálu sa pozrieme na prácu s celými číslami sa znamienkom a bez neho a vysvetlíme si, ako sú také čísla vnútorne v pamäti uložená. Ako asi tušíte, uložená budú v pamäti inak a Assembler nás na rozdiel od vyšších programovacích jazykov od tohto faktu neodstíní. Bez dnešných poznatkov by sme sa teda mnohokrát divili, že sme dostali iné číslo, než sme si mysleli.
Celé čísla v matematike
Všetky čísla sa dajú rozdeliť do skupín (množín). My sa stále budeme baviť o množine celých čísel.
Medzi celé čísla radíme prirodzené čísla, čo sú celé kladné
čísla, ďalej záporné čísla a nulu. V matematike sa celé
čísla označujú Z
. Ako príklad si môžeme uviesť množinu
Z1 = { -3; -2; -1; 0; 1; 2; 3 }
. Súčtom, rozdielom a súčinom
celých čísel dostaneme opäť celé číslo.
Tu asi nie je žiadny problém, poďme sa teda pozrieť, ako to vyzerá v pamäti.
Signed a unsigned čísla v Assemblera
Ako už sme si hovorili v lekcii Assembler
- Dátové typy, v Assemblera síce nerozlišujeme priamo dátové typy, ale
udávame, koľko má ktorá premenná zaberať pamäte. Pre jednoduchosť sa
budeme dnes baviť o premenných o veľkosti 1 bajt (čo je 8 núl a jednotiek).
V tabuľke v danej lekcii bol uvedený rozsah -128
až
255
. Ale to je predsa nezmysel, že? No, vlastne áno aj nie.
Rozsah čísla je myslený tak, že buď môžeme ako 8 núl a jednotiek
uložiť kladné číslo od 0
do 255
alebo záporné
číslo od -128
do 127
. V tomto druhom prípade bude
napr:
- hodnota
-128
uložená v pamäti ako0
(samé nuly, najnižšia hodnota) - hodnota
127
potom ako samé jednotky (zodpovedá hodnote255
len kladných čísiel, s ktorými sme pracovali doteraz).
Jednoducho sme sa dohovorili, že si rozsah posunieme o polovicu dole a razom môžeme ukladať aj záporné čísla.
Ako však spoznáme, či máme s danou adresou v pamäti pracovať ako že má posun alebo nie? Samozrejme nijako, musíme to proste vedieť. Preto používame pre prácu s kladnými a zápornými číslo iné inštrukcie. Pri deklarácii premennej v Assemblera nerozlišujeme niečo ako unsigned bajt a signed bajt, je to jednoducho bajt. Napriek tomu ho môžeme používať na ukladanie buď len kladných čísel alebo kladných aj záporných za cenu zníženia rozsahu na polovicu z každej strany.
Teraz použité termíny kladné a záporné čísla nie sú presné, pretože samozrejme môžeme uložiť kladné číslo s posunom tak, aby mohlo byť trebárs časom záporné, ale teraz bolo kladné. Preto sa v programovaní hovorí o signed a unsigned číslach (čísla so znamienkom a bez znamienka, číslach, ktoré môžu a nemôžu byť záporná).
Unsigned čísla
Ak sa budeme v Assemblera baviť o unsigned číslach, budeme tým myslieť
čísla s rozsahom od 0
do maximálnej možnej hodnoty daného
dátového typu. My sme si povedali, že budeme ako príklad uvádzať bajt.
Konkrétne rozsah unsigned bajtu je teda číslo 0
až
255
. Tu nie je nič čo ďalej riešiť.
Signed čísla
Oproti tomu signed čísla sú akési posunutie celého unsigned rozsahu o
polovicu pod 0
. Posunutím dostaneme pomyselnú os, ktorá bude
obsahovať kladné a záporné čísla. Rozsah signed bajtu je teda číslo
-128
až 127
.
Zápis uvedený v tabuľke dátových typov, -128
až
255
, je teda len ilustratívnym zloženie rozsahu unsigned a signed
bajtu.
Binárne zápis unsigned a signed čísel
Medzi unsigned a signed premennú typu bajt nie je na prvý pohľad
žiadny rozdiel. Obe definujeme pomocou pseudoinstrukce DB
a v binárnej forme tiež nenájdeme žiadny rozdiel. Niečo ale vyčítať
predsa len môžeme, viď ďalej.
Zoberme si binárne číslo:
10000000
O tomto čísle môžeme tvrdiť, že je kladné, ale aj záporné. Prečo?
To, či je číslo kladné, alebo záporné, určuje najvyššie bit. Ak je nastavený, jedná sa o číslo záporné. Pokiaľ nie je nastavený, jedná sa o číslo kladné. Avšak toto platí pre signed čísla. Ak budeme hovoriť o unsigned bajtu, bude sa jednať o kladné číslo bez ohľadu na najvyššie bit.
Čo teda hrá kľúčovú úlohu? Ako už bolo povedané, sú to inštrukcie. Vlastne je na nás, programátoroch, aby sme sa rozhodli, ako s číslom budeme pracovať a samozrejme musíme potom s jednou premennou pracovať stále rovnakým spôsobom.
Tabuľka signed rozsahov
Pre predstavu si môžeme uviesť v tabuľke, ako by vyzerala reprezentácia
signed čísel v pamäti aj za použitia typu Word
(teda 2
bajtov):
typ | kladné čísla | záporné čísla |
---|---|---|
byte | 00000000 (0) - 01111111 (127) | 10000000 (-128) - 11111111 (-1) |
Word | 0000000000000000 (0) - 0111111111111111 (32767) | 1000000000000000 (-32768) - 1111111111111111 (-1) |
Ako príklad si môžeme uviesť skokové inštrukcie JG
a
JA
. Obe robia to isté - ak je prvý operand väčší ako druhý,
vykoná sa skok. Ako ale asi tušíte, JG
uvažuje znamienko, teda
pracuje sa signed číslami.
Inštrukcie JG
Uveďme si jednoduchý zdrojový kód:
mov al, 01111111b ; Přeložíme jako 127. mov bl, 10000000b ; V tomto případě přeložíme jako -128. cmp al, bl jg .al_je_vetsi ret .al_je_vetsi: ret
Inštrukcie CMP
porovná dve čísla. Výstupom je skok na
návestie .al_je_vetsi
, pretože v al
je kladné
číslo a v bl
číslo záporné.
Inštrukcie JA
Teraz si ukážme, čo by sa stalo, keby sme pre záporné číslo použili
inštrukciu JA
, ktorá znamienko neuvažuje:
mov al, 01111111b ; Přeložíme jako 127. mov bl, 10000000b ; V tomto případě přeložíme jako 128. cmp al, bl ja .al_je_vetsi ret .al_je_vetsi: ret
Na .al_je_vetsi
sa teraz nikdy nedostaneme.
Ide len o to, že porovnanie dvoch čísel neprebehne v druhom prípade korektne a skok sa nevykoná.
Inštrukcie pre prácu so signed číslami
Teraz si uvedieme pár základných inštrukcií pre prácu so signed číslami, teda s tými, ktoré môžu byť aj záporná. Všetky inštrukcie sú si veľmi podobné a líšia sa len veľkosťou čísla, s ktorým pracujú.
Znamienkové rozširovanie
Inštrukcie CBW
Inštrukcie CBW
slúži na znaménkovému rozširovania. Táto
inštrukcia znamienkový rozširuje do registra AX
na základe
registra AL
. Rozširovanie sa vykonáva tak, že sa vezme
najvyššiu bit z registra AL
a podľa neho bude register
AH
nadobúdať buď hodnoty 0x00
, ak je najvyššia
bit 0
, alebo 0xFF
, ak má najvyššiu bit hodnotu
1
.
Inštrukcie sa dá použiť nasledovne:
mov al, 10000000b ; V tomto případě přeložíme jako -128. cbw ; Znaménkově rozšíříme do registru AX.
Na výstupe by sme dostali AX = 0xFF80
, čo sa rovná
-128
.
Inštrukcie CWD
Táto inštrukcia je podobná inštrukciu CBW
, ale rozširuje
register AX
do registrového páru DX:AX
. Nemá
žiadny operand.
Inštrukciu môžeme použiť takto:
mov ax, 1000000000000000b ; V tomto případě přeložíme jako -32768. cwd ; Znaménkově rozšíříme do registrového páru DX:AX
Na výstupe dostaneme DX:AX = 0xFFFF8000
, čo je
-32768
.
Inštrukcie CDQ
Inštrukcie CDQ
vykonáva znamienkové rozšírenie registra
EAX
do registrového páru EDX:EAX
. Tiež nemá
žiadny operand.
Používa sa takto:
mov eax, 10000000000000000000000000000000b ; V tomto případě přeložíme jako -2147483648. cdq ; Znaménkově rozšíříme do registrového páru EDX:EAX.
Výstup bude EDX:EAX = 0xFFFFFFFF80000000
a to je
-2147483648
.
Inštrukcie CWDE
Táto inštrukcia vykonáva znamienkové rozšírenie registra
AX
do hornej polovice registra EAX
. Opäť nemá
žiadny operand.
Použijeme ju nasledovne:
mov ax, 1000000000000000b ; V tomto případě přeložíme jako -32768. cwde ; Znaménkově rozšíříme do registru EAX.
Výstup bude EAX = 0xFFFF8000
, čo je -32768
.
Skokové inštrukcie
Ostatné inštrukcie, ktoré môžu pracovať so signed číslami, som si uviedli v niekoľkých predchádzajúcich článkoch. Ešte si uvedieme pár skokových inštrukcií, ktoré uvažujú znamienko.
Inštrukcia JE
(JZ
)
Vykoná skok, ak je prvý operand rovný druhému operandu, alebo je výsledok operácie nula.
Inštrukcia JNE
(JNZ
)
Vykoná skok, pokiaľ nie je prvý operand rovný druhému operandu, alebo výsledok operácie nie je nula.
Inštrukcia JG
(JNLE
)
Vykoná skok, ak je prvý operand väčší ako druhý operand.
Inštrukcia JL
(JNGE
)
Vykoná skok, ak je prvý operand menšia ako druhý operand.
Inštrukcia JNG
(JLE
)
Vykoná skok, ak je prvý operand menší alebo rovný druhému operandu.
Inštrukcia JNL
(JGE
)
Vykoná skok, ak je prvý operand väčší alebo rovný druhému operandu.
Všetky tieto znalosti využijeme pri tvorbe kalkulačky ďalej v kurze. Tam bude nutné používať signed čísla, užívateľ určite očakáva, že ich bude môcť zadať:)
V budúcej lekcii, Assembler - Výpočty s reálnymi číslami , sa naučíme SSE inštrukcie pre prácu s reálnymi číslami.