Până acum, pe parcursul laboratorului de IOCLA am învățat despre cum putem efectua operații cu numere întregi, pozitive sau negative, de diverse dimensiuni. Dar cum rămâne cu numerele cu virgulă? Multe din procesoarele folosite astăzi dispun de o unitate de calcul în virgulă mobilă (Floating Point Unit - FPU), care oferă funcții de calcul pentru astfel de valori.
În acest laborator vom vedea cum putem folosi instrucțiunile puse la dispoziție de FPU pentru a efectua eficient calcule în virgulă mobilă.
Primul aspect pe care trebuie să înțelegem este cum putem să reprezentăm numerele cu virgulă în formă binară. Ca și în cazul numerelor întregi, valorile fracționare se reprezintă pe un anumit număr de biți.
Principalele obiective ale unei reprezentări pe un anumit număr de biți sunt:
Pentru a reprezenta valorile fracționare vom folosi Reprezentarea în virgulă mobilă (Floating Point Representation). În această reprezentare, numerele au următoarea structură:
După cum putem observa mai sus, valorile trebuie transformate astfel încât partea întreagă să fie 1. Această formă poartă numele de formă normală, iar operația de transformare în această formă poartă numele de normalizare.
În forma binară, valorile se reprezintă astfel:
Semnul este dat de primul bit din reprezentarea binară: 1 = negativ, 0 = pozitiv.
Mantisa dă partea fracționară a numărului în forma normală. Numărul de biți pe care mantisa este reprezentată dă precizia maximă a reprezentării.
Baza este de obicei 2, 10 sau 16 și este dată de standardul de reprezentare ales.
Exponentul dă valoarea la care este ridicată baza și numărul de biți pe care este reprezentat dă valorile valorile maxime și minime ce pot fi reprezentate.
Cele mai folosite standarde de reprezentare în virgulă mobilă sunt cu precizie simplă (Single Precision) și cu precizie dublă (Double Precision).
Reprezentarea cu precizie simplă presupune folosirea a 32 de biți și corespunde valorilor float din limbajul C. În acest caz baza folosită este 2, exponentul are 8 biți, iar restul de 23 de biți corespund mantisei.
Reprezentarea cu precizie dublă presupune folosirea a 64 de biți și corespunde valorilor de tip double. În acest caz, baza este 2, exponentul are 11 biți, iar restul de 52 de biți corespund mantisei.
(exponentul pe 8 de biți) - 127
(adică se scade o valoare fixă din valoarea efectivă a exponentului)(exponentul pe 11 de biți) - 1023
(adică se scade o valoare fixă din valoarea efectivă a exponentului)
În ambele standarde de reprezentare, valorile ce au ca exponent 0 sau valoarea maximă (255 pentru precizie simplă și 2047 pentru precizie dublă) sunt valori speciale. Printre acestea se numără:
În procesoarele vechi, calculele matematice erau efectuate pe un chip separat, numit coprocesor matematic care comunica cu procesorul principal. Procesoarele moderne au acest chip încorporat sub forma Floating Point Unit, care le permite să efectueze eficient calcule matematice cu numere fracționare folosind un set separat de instrucțiuni.
Unitatea de calcul în virgulă mobilă deține o stivă proprie pe care o folosește pentru a citi operanzii și a stoca rezultatele operațiilor.
De exemplu, o instrucțiune de tip adunare scoate primele două valori de pe această stivă, efectuează operația de adunare și pune rezultatul înapoi în vârful stivei. Deci, înainte de efectuarea oricărei operații va trebui să punem operanzii necesari pe stivă și, după efectuarea operației, vom obține rezultatul folosind o instrucțiune de tip pop.
Instrucțiune | Descriere |
---|---|
fld1 | Stochează constanta 1 în vârful stivei |
fldz | Stochează constanta 0 în vârful stivei |
fldpi | Stochează constanta π în vârful stivei |
fld DWORD [registru] | Stochează valoarea de tip float (4 octeți) de la adresa indicată de registru |
fild DWORD [registru] | Stochează valoarea de tip integer (4 octeți) de la adresa indicată de registru |
fld QWORD [registru] | Stochează valoarea de tip double (8 octeți) de la adresa indicată de registru |
fld st0 | Duplică valoarea din vârful stivei |
fxch | Interschimbă primele două valori de pe stivă . |
Instrucțiune | Descriere |
---|---|
fstp DWORD [registru] | Citește o valoare de tip float (4 octeți) din vârful stivei și o salvează la adresa indicată de registru. Valoarea este eliminată de pe stivă. |
fst DWORD [registru] | Similar cu instrucțiunea anterioară, dar valoarea rămâne în vârful stivei. |
fstp QWORD [registru] | Citește o valoare de tip double (8 octeți) din vârful stivei și o salvează la adresa indicată de registru, eliminând valoarea de pe stivă |
fst QWORD [registru] | Similar cu instrucțiunea anterioară, dar valoare rămâne în vârful stivei. |
Instrucțiune | Descriere |
---|---|
fcom | Compară primele două valori de pe stivă și setează flag-urile interne FPU. |
fcomi | Similar cu prima instrucțiune, dar setează flag-urile ZF, PF și CF din registrul EFLAGS. |
fcomip | Similar cu prima instrucțiune, dar setează flag-urile și elimină prima valoare din vârful stivei. |
ficom word [registru] | Compara prima valoare de pe stivă cu un număr întreg pe 2 octeți de la adresa indicată de registru. |
ficom dword [registru] | Compară prima valoare de pe stivă cu un număr întreg pe 4 octeți de la adresa indicată de registru. |
ficom qword [registru] | Compară prima valoare de pe stivă cu un număr întreg pe 8 octeți de la adresa indicată de registru. |
ficomp word/dword/qword [registru] | Similar cu instrucțiunile anterioare, dar elimină și prima valoare de pe stivă. |
ftst | Compară prima valoare de pe stivă cu 0.0. |
ftst ; compară valoarea din vârful stivei cu +0.0 fstsw ax ; copiază registrul de stare intern FPU-ului în AX fwait ; așteaptă completarea instrucțiunii precedente sahf ; copiază flag-urile din AX în registrul de stare EFLAGS ; acum flag-urile sunt setate în funcție de rezultatul instrucțiunii FTST jg <LABEL>
Instrucțiune | Descriere |
---|---|
fabs | Înlocuiește prima valoare de pe stivă cu valoarea ei absolută. |
fchs | Change sign - schimbă semnul valorii din vârful stivei. |
frndint | Round to integer - rotunjește prima valoare de pe stivă la întreg. |
fadd dword/qword [registru] | Adună prima valoare de pe stivă cu cea indicată de adresa din registru (pe 4/8 octeți). |
fdiv dword/qword [registru] | Împarte prima valoare de pe stivă cu cea indicată de adresa din registru (pe 4/8 octeți) |
fdivr dword/qword [registru] | Similar cu instrucțiunea precedentă, dar împărțirea este inversă. |
fmul dword/qword [registru] | Înmulțește prima valoare de pe stivă cu cea indicată de adresa din registru (pe 4/8 octeți). |
fsub dword/qword [registru] | Scade din prima valoare de pe stivă valoarea indicată de adresa din registru (pe 4/8 octeți). |
fsubr dword/qword [registru] | Similar cu instrucțiunea anterioară, dar ordinea operanzilor se schimbă |
fsqrt | Înlocuiește prima valoare de pe stivă cu rădăcina ei pătrată |
fsin | Înlocuiește prima valoare de pe stivă cu rezultatul funcției sin. |
fcos | Înlocuiește prima valoare de pe stivă cu rezultatul funcției cos. |
Instrucțiunile fiadd
, fisub
, fidiv
, fimul
funcționează exact ca cele din tabelul de mai sus, dar primesc ca argument o valoare întreagă de 2/4 octeți (word/dword).
Lista completă a instucțiunilor poate fi văzută aici
Mai jos este prezentat un exemplu de adunare a două numere cu virgulă, reprezentate pe 8 octeți (de tip double). Am folosit valori double pentru a putea folosi printf pentru afișare, întrucât acesta nu poate afișa valori cu precizie simplă.
%include "io.inc" extern printf section .data n1 dq 1.1 n2 dq 4.3 format db "%f", 10, 0 section .text global CMAIN CMAIN: mov ebp, esp fld qword [n1] fadd qword [n2] sub esp, 8 ; rezervă loc pe stivă pentru rezultat, pasat ca argument funcției printf. fstp qword [esp]; mută rezultatul adunării în spațiul rezervat. push format call printf add esp, 12 ret
Exemplul de mai jos citește o valoare de tip double de la tastatură și afișează rezultatul expresiei sin(x * pi).
%include "io.inc" extern printf extern scanf section .data x dq 0.0 scan_format db "%lf" print_format db "sin(pi * x) = %f", 10, 0 section .text global CMAIN CMAIN: mov ebp, esp push x push scan_format call scanf add esp, 8 fldpi fmul qword [x] fsin sub esp, 8 fstp qword [esp] push print_format call printf add esp, 12 ret
Pentru a îmbunătăți cursul de IOCLA, componentele sale și modul de desfășurare, ne sunt foarte utile opiniile voastre. Pentru aceasta, vă rugăm să accesați și să completați formularul de feedback de pe site-ul acs.curs.pub.ro, la cursul specific seriei din care faceți parte. Trebuie să fiți autentificați și înrolați în cadrul cursului.
Formularul este anonim și este activ în perioada 6-17 ianuarie 2020. Rezultatele vor fi vizibile în cadrul echipei cursului doar după încheierea sesiunii.
Vă invităm să evaluați activitatea echipei de IOCLA și să precizați punctele tari și punctele slabe și sugestiile voastre de îmbunătățire a disciplinei. Feedback-ul vostru este foarte important pentru noi să creștem calitatea materiei în anii următori și să îmbunătățim disciplinele pe care le veți face în continuare.
Ne interesează în special:
Vă mulțumim!
Formularele de feedback sunt următoarele:
Pentru exercițiile din acest laborator vom folosi fișierele din această arhivă. Rezolvarea exercițiilor o vom face în SASM.
În fișierul suma.asm
din subdirectorul ex1/
găsiți un exemplu de adunare a valorilor dintr-un vector de numere fracționare. La final, rezultatul este afișat folosind funcția printf
.
Parcurgeți și înțelegeți exemplul dat. Deschideți-l folosind SASM și rulați-l.
Acum că am văzut cum putem calcula suma elementelor unui vector, ne propunem să calculăm media acestora. Pentru aceasta trebuie să calculăm mai întai suma (vă puteți inspira din exemplul anterior) și să împărțim la numărul de elemente.
Urmăriți comentariile marcate cu TODO
din fișierul media.asm
din subdirectorul ex2/
și completați corespunzător pentru calcularea mediei vectorului de elemente vector
.
vector
). Folosiți instrucțiunea fidiv
pentru a împărți valoarea din vârful stivei FPU la un număr cu reprezentare de număr întreg.
În fișierul integer-div.asm
din subdirectorul ex3/
, completați locurile marcate cu TODO
pentru a efectua împărțirea numerelor întregi n1
și n2
, iar rezultatul să fie un număr fracționar. La final, afișați rezultatul.
fidiv
.
Deîmpărțitul trebuie să fie încărcat în prealabil (înaintea împărțirii folosind fidiv
) pe stiva FPU tot ca întreg. Pentru a încărca un număr cu reprezentare ca număr întreg pe stiva FPU folosiți instrucțiunea fild
.
Completați secțiunile marcate cu TODO
din fișierul extract.asm
din subdirectorul ex4/
, pentru a extrage partea întreagă și partea fracționară a numărului fracționar n
.
fisttp dword <adresa>
, care extrage prima valoare din vârful stivei FPU, o trunchiază și o stochează ca un întreg la adresa specificată.
fisttp
este o instrucțiune validă, chiar dacă nu este colorată în SASM
.
fisttp
face și pop la valoarea din vârful stivei FPU. De aceea, pentru obținerea părții fracționare, va trebui să reîncărcați valoarea numărului fracționar n
(folosind fld
) înainte de scădea valoarea întregii din acesta folosind instrucțiunea fisub
.
Similar cu exercițiul 2, ne propunem să calculăm media unui vector de elemente, dar în acest caz valorile sunt întregi. Rezultatul trebuie, bineînțeles, să fie fracționar. Completați locurile marcate cu TODO
din fișierul media-int.asm
, directorul ex5
.
fiadd
care adună numere în reprezentare de număr întreg.
De asemenea, când faceți împărțirea, veți folosi instrucțiunea fidiv
.
Rezultatul operației de împărțire va fi unul fracționar, deci îl veți recupera folosind instrucțiunea fstp
.
Completați fișierul max.asm
din subdirectorul ex6/
pentru a afla valoarea maximă din vector. Parcurgerea elementelor și comparația cu valoarea maximă este implementată. Urmăriți comentariile ce conțin TODO
.
fcomip
urmăriți răspunsul de aici.
fstp ST0
.
max
trebuie să ajungă pe stivă. Întrucât valoarea are 8 octeți sunt necesare două operații de tip push dword ...
. Întâi faceți push la ultimii 4 octeți ai valorii variabilei max
și apoi la primii 4 octeți. Astfel vârful stivei va referi primii 4 octeți ai valorii variabilei max
urmați de ultimii 4 octeți.
În fișierul arcsin.asm
din subdirectorul ex7/
, calculați valoarea unghiului pentru care funcția sin întoarce valoarea din variabila valoare_sin
.
Pentru rezolvare veți căuta în intervalul [0, pi/2], folosind metoda bisecției: la fiecare pas calculați valoarea funcției sin în valoarea de la jumătatea intervalului și alegeți jumătatea de interval în care se găsește valoarea căutată (funcția este crescătoare pe intervalul [0, pi/2]). Algoritmul se va opri atunci când eroarea este mai mică decât 0.0005
(diferența între valoarea obținută și valoarea căutată).