Le istruzioni per fare matematica

In questo aticolo analizzeremo le istruzioni che l'Assembly mette a
disposizione per effettuare calcoli matematici(somme, sottrazioni,...) nonchè
quelle che permettono di manipolare i dati a livello di bit(AND, OR,...)

>ADD, ADC e INC
La prima operazione matematica che si imapara alle elementari è senza dubbio la
somma ed anche io comincio con quella.
Le istruzioni che eseguono la somma sono tre :

        ADD < registro|memoria >,< registro|memoria|valore >
        ADC < registro|memoria >,< registro|memoria|valore >
        INC < registro|memoria >

NOTA:dato che i trasferimenti diretti memoria-memoria non sono possibili i due
operandi non possono essere entrambi locazioni di memoria.

Perchè 3 istruzioni ?
ADD op1,op2 effettua la somma tra op1 e op2 e mette il risultato in op1
ADC op1,op2 effettua la somma tra op1 e op2, mette il risultato in op1 e se c'è
riporto somma 1 al risultato
INC op  incrementa di 1 op; il numero è considerato senza segno quindi non 
viene modificato il valore del CF in caso di riporto.

Queste operazioni effettuano una somma a prescindere dal fatto che si stia
lavorando con numeri con segno o senza segno, sta al programmatore interpretare
il valore della somma.
Sta anche al programmatore il compito di controllare il risultato in caso di
OVERFLOW (quando il valore della somma non sta nei registri) analizzando il
valore del bit di overflow nel Flag Register.
Vediamo alcuni esempi

        .DATA
dato    DB      39              ;1 byte
        .CODE
        ...
        ...
        mov     al,26           ;al=26
        inc     al              ;al=27
        add     al,76           ;al=103

        add     al,dato         ;al=142 oppure al=-114 (se con segno!!)

        mov     ah,al           ;ah=142, al=142
        add     al,ah           ;al=28 con CF=1
        ...
        ...

 Tramite l'istruizone ADC è possibile effettuare somme che non stanno in un
 unico registro:

        .DATA
dato32  DD      316423
        .CODE
        ...
        ...
        mov     ax,43981                ;ax=43981
        sub     dx,dx                   ;dx=0
        add     ax,WORD PTR dato32[0]   ;somma ax ai 2 byte meno significativi
                                        ;  di dato32
        adc     dx,WORD PTR dato32[2]   ;somma ax ai 2 byte piu' significativi
                                        ;  di dato32 tenendo conto del riporto
        ...
        ...

In questo caso il risultato è contenuto in DX:AX

>SUB, SBB, DEC e NEG
Dopo l'addizione non poteva mancare la sottrazione!
La sintassi per queste operazioni è la seguente:

        SUB     < registro|memoria >,< registro|memoria|valore >
        SBB     < registro|memoria >,< registro|memoria|valore >
        DEC     < registro|memoria >
        NEG     < registro|memoria >  

Cominciamo dalla SUB che funziona esattamente come la ADD: sottrae dal primo
operando il secondo e mette il risultato nel primo. Valgono le stesse
osservazioni fatte per la somma quindi se non ve le ricordate andate a
rileggerle.
Idem per quanto riguarda la DEC che altro non fa che sottrarre 1 all'operando,
anche qui il numero è trattato come senza segno.
Vi riporto anche qui qualche esempio per capire meglio:
        .DATA
dato    DB      122
        .CODE
        ...
        ...
        mov     al,95           ;al=95
        dec     al              ;al=94
        sub     al,23           ;al=71

        sub     al,dato         ;al=-51 se con segno oppure al=205 senza segno

        mov     ah,119
        sub     al,ah           ;al=86  con OF=1

Quando il risultato va sotto lo zero viene settato il SF (sign flag).
Per poter effettuare le sottrazioni vere e proprie con tanto di riporto si usa
la SBB (subtract with borrow) che funziona esattamente come la SUB solo che in
caso di riporto viene sottratto 1 al risultato.
Esempio:

        .DATA
datoA   DD      316423
datoB   DD      156739
        .CODE
        ...
        ...
        mov     ax,WORD PTR datoA[0]    
        mov     dx,WORD PTR datoA[2]    ;ora ho il primo numero in DX:AX
        sub     ax,WORD PTR datoB[0]    
        sub     dx,WORD PTR datoB[2]    ;ho il risultato in DX:AX
        ...
        ...

L'ultima istruzione che rientra nella categoria delle sottrazioni è la NEG che
cambia il segno dell'operando e che quindi deve essere usata solo per i numeri
con segno.
ES:

        mov     ax,143  ;ax=143  --> 8Fh
        neg     ax      ;ax=-143 --> FF71h

NOTA: Vi ricordo che i numeri negativi sono rappresentati in complemento a2.

>MUL e IMUL
La terza operazione che vediamo è la moltiplicazione, senza segno (MUL) e con
segno (IMUL).
La loro sintassi è la seguente:

        MUL     < registro|memoria >
        IMUL    < registro|memoria >

Vi chiederete dove sta il secondo fattore. Bene l'operando specificato nella 
MUL (o nella IMUL) verra moltiplicato per il numero che è contenuto
nell'accumulatore (AX per i 16bit, AL per gli 8bit o EAX per i 32bit).
E il risultato?
Quello dipende dagli operandi:

        1byte * AL --> risultato in AX  (CF=0 e OF=0 se e solo se AH=0)
        2byte * AX --> risultato in DX:AX (CF=0 e OF=0 se e solo se DX=0)
        4byte * EAX -> risultato in EDX:EAX (CF=0 e OF=0 se e solo se EDX=0)

Anche qui vediamo alcuni esempi:

        .DATA
dato    DW      -30000
        .CODE
        ...
        ...
        mov     al,23           ;al=23
        mov     bl,24           ;bl=24
        mul     bl              ;ax=552 (CF=1 e OF=1)

        mov     ax,50
        imul    dato            ;DX:AX=-1500000 (CF=1 e OF=1)

Da questo esempio si capisce anche come funziona la IMUL.

Per processori 80186 e superiori la IMUL prevede anche le seguenti sintassi:

        IMUL    < registro >,< valore >
        IMUL    < registro >,< memoria >,< valore >

In questo caso il risultato viene messo nel registro specificato e nel caso non
ci stia viene settato CF e OF.
Quando specifico 3 parametri il primo è la destinazione e gli altri due sono i
fattori del prodotto.
Esempi:

        imul    dx,455          ;moltiplica dx*455 
        imul    ax,[bx],6       ;moltiplica il valore puntato da bx*6 e
                                ; mette il risultato in ax

>DIV e IDIV
Anche per la divisione esistono 2 istruzioni una per i numeri senza segno (DIV)
e una per quelli col segno (IDIV).
Entrambe ritornano il quoziente e il resto, e la loro sintassi è:

        DIV     < registro|memoria >
        IDIV    < registro|memoria >

Per effettuare una divisione di un numero a 16bit per uno di 8bit devo mettere
il numero da dividere in AX e specificare il divisore (cioè l'altro numero) in
un altro registro. Il risultato della divisione sara in AL e il resto in AH ;
quindi fate attenzione il dividendo viene perso.
Se invece voglio dividere un numero a 32 bit per uno di 16 si deve mettere il
dividendo in DX:AX, specificare il divisore in un registro a 16 bit (che non sia
AX o DX) ed effettuare la divisione: avremo il risultato in AX e il resto in
DX.
Per dividere tra loro due numeri a 16 bit si deve prima trasformare il dividendo
in un numero a 32 bit tramite l'istruzione CWD (andate a rivederla) se ci
interessa il segno oppure nel modo visto nel tutorial precedente se stiamo
lavorando con numeri senza segno.
Se si cerca di effettuare una divisione per 0 o se il quoziente non sta nei
registri viene generata una chiamata all'int 0 ,il programma termina e il
controllo torna al DOS.
Per evitare questo problema ci sono due modi: o si controlla il valore del
divisore prima di effettuare l'istruzione DIV, oppure intercettare l'int 0 e
riscriversi una routine di interrupt per gestire l'evento ma questo lo vedremo
piu' avanti!!
Vi riporto quelche esempio:
        .DATA
data16  DW      -2000
data32  DD      500000
        .CODE
        ...
        ...
        mov     ax,700  ;ax=700
        mov     bl,36   ;bl=36
        div     bl      ;al=19 (quoziente), ah=16 (resto)

        mov     ax,WORD PTR dato32[0]
        mov     dx,WORD PTR dato32[2]   ;carico il dividendo in DX:AX
        idiv    dato16                  ;500000/-2000
                                        ;AX=-250 , DX=0
        mov     ax,WORD PTR dato16
        cwd                             ;lo estendo a 32bit in DX:AX
        mov     bx,-421
        idiv    bx                      ;ax=4 , dx=-316


Ora che abbiamo visto le 4 operazioni fondamentali analizzeremo alcune
istruzioni che lavorano con i numeri binari in notazione BCD.
Vi chiederete perche; beh sappiate che lavorare con in numeri in BCD spesso
è piu' facile per fare calcoli.
Queste istruzioni non hanno operandi perche lavorano sempre con al:

>AAA (Adjust after an Addiction)
Per capire cosa fa vediamo un esempio:

        mov     ax,9    ;ax=9
        mov     bx,3    ;bx=3
        add     al,bl   ;al=C
        aaa             ;al=2 ah=1  CF=1

Come si vede dall'esempio il risulato (in "unpacked" BCD) l'ho in ax: in ah ci
sono le decine e in al le unità. Notate che setta anche il Carry a 1.
Ricordatevi che una cifra in decimale viene qui codificata con 1byte.

>AAS (Adjust After a Subctraction)

        mov     ax,103h ;ax=13h  (in BCD : ah=1 al=3  !!!)
        mov     bx,4    ;bx=4
        dub     al,bl   ;al=FFh   (-1)
        aas             ;al=9   ah=0    CF=1

>AAM (Adjust After a Multiplication)

        mov     ax,903h 
        mul     ah      ;moltiplica ah*al = 1Bh
        aam             ;ah=2 al=7;

Attenzione : questa istruzone si può usare solo per le moltiplicazioni senza
segno !!!

>AAD  (Adjust BEFORE a division)
A differenza delle altre 3 questa va usata prima di una divisione infatti
trasforma il numero in BCD in codica binario prima dell'operazione.
Dopo la divisione il risultato va risistemato tramite un AAM.
Vediamo ad esempio (25/2):

        mov     ax,205h ;carico il dividendo (25)
        mov     bl      ;carico il divisore  (2)
        aad             ;trasformao il 25 in BCD in 19h binario (ax=19h)
        div     bl      ;quoziente in al=0Ch resto in ah=1
        aam             ;sistema al in ax=102h (12 in BCD)

Notate che il resto viene perso: se vi serve ricordatevi di slvarlo prima
dell'istruzione AAM.

Abbiamo visto come si lavora con numeri a 2 cifre ma se devo usare numeri piu'
grossi?
Semplice devo iterare il metodo per ogni cifra del numero !!

Esistono poi altre due istruzioni per lavorare con un altro tipo di numeri BCD
cosiddetti "packed" in cui ogni cifra occupa solo 4 bit.
A differenza delle altre non cambiano il valore di ah ma lavorano sul registro
di flag ogni volta che c'è un riporto.

>DAA (Adjust After an Addiction)

        mov     ax,8833h        ;carica 88 e 33 in un unico registro
        add     al,ah           ;0BBh
        daa                     ;CF=1 e AL=21

>DAS (Adjust After an Subctraction)
        mov     ax,3883h        ;carica 38 e 83 in BCD
        sub     al,ah           ;04Bh
        das                     ;CF=0  AL=45

Vediamo ora le istruzioni che il processore ci mette a disposizione per
effettuare operazioni logiche.
Qui andrò abbastanza veloce, il significato degli operatori l'ho già spiegato
perciò mi limiterò a elencare la sintassi delle istruzioni e a fare qualche
breve commento.

>AND < registro|memoria >,< registro|memoria|valore >
Effettua un AND bit a bit degli operandi.
L'AND può essere utile per azzerare alcuni bit di un registro tramite una
"maschera" in cui i bit da azzerare sono 0 e gli altri sono 1:

        mov     ax,035h         ;00110101
        mov     bx,0FBh         ;11111011
        and     ax,bx           ;00110001   ho azzerato il secondo bit !!

>OR < registro|memoria >,< registro|memoria|valore >
Effettua l'OR bit a bit.
Può essere utile per settare solo alcuni bit di un registr con un'opportuna
"maschera" di bit.

>XOR  < registro|memoria >,< registro|memoria|valore >
Effettua lOR esclusivo bit a bit.
E' molto usata per azzerare il valore di un registro facendo XOR con se stesso:
è molto veloce!!

>NOT < registro|memoria >
Effettua il NOT logico sui bit dell'operando.

Vediamo infine alcune istruzioni non prettamente matematiche ma che vengono
spesso utilizzate per questo scopo.
>SHL e SHR
Queste due istruzioni shiftano a sinistra e destra (risp.) l'argomento.
Innanzi tutto la sintassi è la seguente:

        SHL  < registro|memoria >,< CL|1 >
        SHR  < registro|memoria >,< CL|1 >
Vediamo cosa vuol dire con esempio:

        mov     al,01001011     ;al=01001011
        shl     al,1            ;al=10010110  CF=0

In pratica è successo che tutti i bit sono stati spostati di una posizione
verso sx, il bit più significativo è andato nel CF e quello meno significativo
è stato messo a zero.
Dal punto di vista matematico ho effettuato una moltiplicazione per 2!!
Lo stesso discorso simmetrico vale per lo Shift a dx e in questo caso il numero
viene diviso per 2.
Se sposto di due posizioni il numero verra moltiplicato/diviso per 4, se sposto
di 3 per 8,di 4....

>ROR e ROL
Queste servono per far ruotare i bit a destra e sinistra  (risp.)
La loro sintassi è uguale a shl e shr (non sto a riscriverla!).
Vediamo subito un esempio:

        mov     al,01001011     ;al=01001011
        ror     al,1            ;al=10100101
        mov     cl,3
        rol     al,cl           ;al=00101101


Bene anche questa puntata è finita. Ora dovreste avere i mezzi per scrivere
qualche semplice routine matematica.
E' importante che abbiate capito come vengono rappresentati i numeri all'interno
di un calcolatore quindi andate a rileggervi il primo capitolo sui numeri binari
ponendo particolare attenzione ai numeri negativi.


** [email protected] **