IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

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 -128255. 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 ako 0 (samé nuly, najnižšia hodnota)
  • hodnota 127 potom ako samé jednotky (zodpovedá hodnote 255 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 0255. 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 -128127.

Zápis uvedený v tabuľke dátových typov, -128255, 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)
Príklad - Skok na základe porovnania 2 čísiel

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.


 

Predchádzajúci článok
Assembler - Registre procesora
Všetky články v sekcii
Základy assembleri
Preskočiť článok
(neodporúčame)
Assembler - Výpočty s reálnymi číslami
Článok pre vás napísal Jakub Verner
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Autor se věnuje programování v x86 Assembleru.
Aktivity