Assembly Base con NASM
Capitolo 18: Istruzioni aritmetiche per i numeri BCD
Nel precedente capitolo sono stati illustrati diversi esempi relativi ad
istruzioni aritmetiche applicate a numeri interi di ampiezza arbitraria;
abbiamo potuto così constatare che, se i numeri su cui si deve operare
vengono espressi in binario (cioè, nello stesso codice utilizzato dalla
CPU), si ottengono procedimenti di calcolo caratterizzati da una
notevole semplicità.
La stessa semplicità viene riscontrata anche quando operiamo su numeri
interi espressi in notazione esadecimale; del resto, sappiamo bene che lo
scopo fondamentale della notazione esadecimale, è quello di permettere
la rappresentazione dei numeri binari in un formato molto più compatto
e molto più comodo da maneggiare.
La totale compatibilità tra notazione binaria e notazione esadecimale, è
una diretta conseguenza del fatto che, come sappiamo,
16=24. Grazie a questa proprietà matematica, risulta
che una singola cifra esadecimale, corrisponde ad un gruppo di 4
cifre binarie (1 nibble); viceversa, un gruppo di 4 cifre
binarie, corrisponde ad una singola cifra esadecimale.
Il problema che si presenta è dato dal fatto che, mentre il computer è
stato progettato per lavorare in base 2, noi esseri umani siamo,
invece, abituati a lavorare in base 10 ed incontriamo notevoli
difficoltà nell'utilizzo di altre basi numeriche; inoltre, sappiamo che
il dialogo tra computer ed esseri umani è reso ancora più difficile dal
fatto che il numero 10, non può essere espresso come
2n (potenza di 2 con esponente n intero
positivo). Se provassimo allora a riscrivere gli algoritmi presentati nel
precedente capitolo, in modo da adattarli ai numeri interi in base 10,
andremmo incontro a notevoli complicazioni; si rende necessaria, quindi, la
ricerca di una adeguata soluzione a questo importante problema.
In generale, la strada migliore da seguire consiste nel cercare di raggiungere
un compromesso, tale da soddisfare le esigenze, sia del computer, sia
dell'utente; a tale proposito, nella eventualità di dover compiere delle
elaborazioni su una serie di dati numerici, possiamo seguire un procedimento
che può essere suddiviso nelle seguenti fasi:
- input dei dati espressi in base 10
- conversione dei dati in base 2 (o in base 16)
- esecuzione delle elaborazioni sui dati
- conversione dei risultati in base 10
- output dei risultati espressi in base 10
Questo procedimento, soddisfa le esigenze dell'utente in quanto, sia la
fase di input, sia la fase di output, si svolgono con l'ausilio della base
10; allo stesso tempo, vengono soddisfatte anche le esigenze della
CPU che può eseguire le necessarie elaborazioni su dati espressi
in base 2.
18.1 Rappresentazione dei numeri interi decimali in formato BCD
Tra i vari metodi adottati per permettere alla CPU di maneggiare
facilmente i numeri interi in base 10, si può citare, in particolare,
il sistema di rappresentazione in formato BCD; la sigla BCD sta
per Binary Coded Decimal (decimale codificato in binario).
Questo sistema di codifica si basa sulla semplice constatazione che un
numero esadecimale, formato però esclusivamente da cifre comprese tra
0h e 9h, ci appare formalmente come se fosse espresso in
base 10; dato, ad esempio, il numero decimale:
39674253
possiamo pensare di rappresentarlo come:
39674253h
Attraverso questa tecnica quindi, la CPU si serve della base
16 per maneggiare con estrema semplicità ed efficienza, numeri
interi decimali di ampiezza arbitraria che, formalmente, ci appaiono
come se fossero espressi in base 10; in sostanza, la codifica
BCD permette di estendere alla base 10, tutti i vantaggi
che derivano dall'uso dei numeri esadecimali (vantaggi che abbiamo
studiato nei precedenti capitoli).
La codifica BCD ha avuto una grande diffusione, soprattutto in
campo finanziario, dove si presenta la necessità di gestire con il computer
i bilanci delle aziende, rappresentati spesso da numeri interi di grosse
dimensioni; l'utilizzo della base 10 per la gestione col computer di
grossi numeri interi (e delle operazioni matematiche ad essi applicate),
comporta notevoli complicazioni che possono essere superate, appunto, con
l'ausilio della codifica BCD.
Anche per altri aspetti, come l'interfacciamento tra PC e strumenti
di misura o il salvataggio nella memoria CMOS delle informazioni
relative alla configurazione hardware del PC, si fa largo uso del
sistema di codifica BCD (la memoria CMOS del PC, viene
trattata nella sezione Assembly Avanzato); possiamo dire quindi che
un programmatore Assembly che si rispetti, non può fare a meno della
conoscenza di questo importante argomento.
Tornando all'esempio presentato in precedenza, abbiamo visto che,
formalmente, i due numeri 39674253 e 39674253h, appaiono
identici; in realtà, sappiamo bene che il numero esadecimale
39674253h, tradotto in base 10 corrisponde, non a
39674253, bensì a:
(3 * 167) + (9 * 166) + (6 * 165) + (7 * 164) + (4 * 163) + (2 * 162) + (5 * 161) + (3 * 160)
cioè:
963068499
Da questa semplice considerazione, si intuiscono subito i problemi che bisogna
affrontare con i numeri BCD; infatti, appare evidente il fatto che,
eseguendo addizioni, sottrazioni, moltiplicazioni e divisioni sui numeri
BCD, si ottengono risultati che hanno senso in base 16, ma non
in base 10!
Consideriamo, ad esempio, una somma tra i due numeri 35 e 49;
supponendo che questi due numeri siano espressi in base 10, otteniamo:
35 + 49 = 84
Supponendo, invece, che questi due numeri siano espressi in formato
BCD (e quindi, in base 16), otteniamo:
35h + 49h = 7Eh
È chiaro quindi che dopo aver eseguito l'addizione tra numeri BCD,
abbiamo bisogno di un procedimento che ci permetta di trasformare 7Eh
in 84h, simulando così il risultato che si ottiene in base 10;
lo stesso discorso vale anche per le sottrazioni, moltiplicazioni e divisioni
tra numeri BCD.
Tutte le CPU della famiglia 80x86 e tutte le FPU della
famiglia 80x87, sono dotate di apposite istruzioni esplicitamente
rivolte alla gestione dei numeri BCD; prima di poter conoscere in
dettaglio queste istruzioni, dobbiamo analizzare le convenzioni che vengono
adottate per la rappresentazione dei numeri BCD.
18.1.1 Rappresentazione dei numeri interi decimali in formato Unpacked BCD
Come è stato ribadito all'inizio del capitolo, grazie all'eguaglianza
16=24, una qualsiasi cifra esadecimale equivale ad un
gruppo di 4 bit (nibble); tale equivalenza è illustrata in Figura 18.1.
La Figura 18.1 evidenzia il fatto che se, ad esempio, vogliamo tradurre in
binario il numero esadecimale ADh, non dobbiamo fare altro che
affiancare i due nibble 1010b e 1101b (che corrispondono a
Ah e Dh), in modo da ottenere 10101101b; questa tecnica
non può essere, invece, applicata ai numeri in base 10, in quanto
10 non è una potenza di 2 con esponente intero positivo.
Per ovviare a questo problema, possiamo pensare di sfruttare l'equivalenza
Hex. Bin. illustrata in Figura 18.1, rappresentando una qualunque
cifra decimale compresa tra 0 e 9, attraverso la corrispondente
cifra esadecimale; ogni cifra esadecimale così ottenuta, può essere poi
memorizzata nel nibble meno significativo di una locazione da 1 byte.
Consideriamo, ad esempio, il numero decimale 38126987; "simulando"
questo numero in base 16, otteniamo 38126987h. A questo
punto, creiamo una locazione di memoria da 8 byte e disponiamo
le 8 cifre esadecimali di 38126987h, negli 8 nibble
bassi di ciascun byte della locazione stessa. Nell'ipotesi che la locazione
di memoria si trovi all'offset 002Ch di un segmento dati, otteniamo
la situazione illustrata in Figura 18.2; osserviamo che, nel rispetto della
convenzione little endian, la cifra meno significativa del numero
BCD occupa l'indirizzo più basso.
Come possiamo notare, il nibble più significativo di ogni BYTE
rimane inutilizzato e deve valere 0h (0000b); questo tipo di
codifica dei numeri interi in base 10, viene definito Unpacked
BCD (BCD scompattato).
Nella terminologia utilizzata dalla Intel, ogni cifra di un numero
Unpacked BCD viene definita ASCII digit (cifra ASCII);
il perché di questa definizione verrà chiarito nel seguito del capitolo.
18.1.2 Rappresentazione dei numeri interi decimali in formato Packed BCD
Come si può notare in Figura 18.2, la codifica Unpacked BCD comporta
un notevole spreco di memoria; tuttavia, i numeri Unpacked BCD
presentano il vantaggio di poter essere gestiti in modo molto semplice
dalla CPU.
Per garantire una rappresentazione più compatta dei numeri BCD, è
stata definita anche un'altra codifica basata sul fatto che, ogni cifra
esadecimale occupa un solo nibble; di conseguenza, in ogni locazione da
1 byte possiamo inserire 2 cifre esadecimali.
In questo modo, il numero 38126987 dell'esempio di Figura 18.2, viene
disposto in memoria come illustrato in Figura 18.3; si noti che anche in questo
caso, viene rispettata la convenzione little endian.
Questo tipo di codifica dei numeri interi in base 10, viene definito
Packed BCD (BCD compattato); confrontando la Figura 18.3 con la
Figura 18.2, si nota che un numero in versione Packed BCD occupa la
metà della memoria rispetto alla versione Unpacked BCD.
Nella terminologia utilizzata dalla Intel, ogni cifra di un numero
Packed BCD viene definita decimal digit (cifra decimale).
18.1.3 Rappresentazione dei numeri interi decimali in formato Packed BCD standard
Per motivi pratici, la Intel ha stabilito una serie di convenzioni
che definiscono un formato standard per i numeri Packed BCD; in base
a tali convenzioni, un numero Packed BCD standard occupa in memoria
una locazione da 10 byte (1 tenbyte).
Internamente, una locazione di memoria da 10 byte riservata ad un
numero Packed BCD standard, viene organizzata secondo lo schema di
Figura 18.4; i simboli Ci (con i che va da 0
a 17), rappresentano le cifre (ciascuna compresa tra 0h e
9h) del numero Packed BCD.
Come si può notare, i 9 byte meno significativi della locazione di
memoria, contengono sino a 18 cifre di un numero in formato Packed
BCD; tali cifre risultano disposte, come al solito, secondo la convenzione
little endian.
Eventuali cifre non utilizzate, devono valere obbligatoriamente 0h; ad
esempio, se il numero utilizza solo le prime 8 cifre (da
C0 a C7), tutte le altre cifre (da
C8 a C17) devono valere 0h.
Il byte più significativo della locazione di memoria (decimo byte), codifica
il segno del numero BCD; il segno positivo è rappresentato dal codice
00h (00000000b), mentre il segno negativo è rappresentato dal
codice 80h (10000000b).
Possiamo dire quindi che il formato Packed BCD standard illustrato in
Figura 18.4, permette di rappresentare numeri interi decimali compresi tra:
-999999999999999999
e
+999999999999999999
Risulta, inoltre, che il numero zero può essere rappresentato in due modi e cioè:
-000000000000000000
e
+000000000000000000
Tutte le FPU della famiglia 80x87, sono in grado di gestire,
direttamente, numeri interi in formato Packed BCD standard; a tale
proposito, vengono rese disponibili le due istruzioni FBLD e
FBSTP.
L'istruzione FBLD (Fast Packed BCD Load), legge un numero
Packed BCD standard dalla RAM e lo carica in un registro
della FPU; qualsiasi dato caricato in un registro della FPU,
viene convertito in un formato floating point da 10 byte,
chiamato Temporary Real.
I numeri in formato Temporary Real, possono essere utilizzati dalla
FPU come operandi di espressioni algebriche, logaritmiche,
trigonometriche, esponenziali, etc; i risultati che scaturiscono dalle
elaborazioni, possono essere poi convertiti dalla FPU, in diversi
formati.
In particolare, l'istruzione FBSTP (Fast Packed BCD Store and
Pop), legge un numero Temporary Real da un registro della
FPU e lo memorizza nella RAM in formato Packed BCD
standard.
Tutti i dettagli relativi al funzionamento della FPU e al relativo
set di istruzioni, vengono illustrati nella sezione Assembly Avanzato.
Da quanto è stato appena esposto, appare evidente che se disponiamo di
una FPU, possiamo gestire i numeri Packed BCD standard in
modo estremamente semplice e veloce; fortunatamente, tutte le CPU,
a partire dalla 80486 DX, sono dotate di FPU incorporata.
Anche le CPU della famiglia 80x86, dispongono di apposite
istruzioni dedicate espressamente ai numeri BCD; tali istruzioni,
però, operano esclusivamente sulle singole cifre di un numero Packed
BCD o Unpacked BCD.
Lo scopo di queste istruzioni è quello di "aggiustare" il risultato di
addizioni, sottrazioni, moltiplicazioni e divisioni, eseguite su singole
cifre di numeri BCD; il compito di scrivere tutto il codice
necessario per operare su numeri BCD di ampiezza arbitraria, viene
lasciato quindi al programmatore. In particolare, se vogliamo utilizzare
la CPU per operare su numeri Packed BCD standard, dobbiamo
gestire via software tutte le situazioni che si possono presentare in
relazione al segno degli operandi; ad esempio, dati i due numeri +A
e -B in formato Packed BCD standard, se vogliamo calcolare
una somma tra +A e -B, con B maggiore di A in
valore assoluto, dobbiamo sfruttare il fatto che:
(+A) + (-B) = (+A) - (+B) = -((+B) - (+A))
Dopo aver calcolato quindi la differenza tra +B e +A, dobbiamo
assegnare il segno negativo (codice 80h) al risultato.
Tutto ciò è una diretta conseguenza del fatto che, il formato Packed
BCD standard, ha il solo scopo di rappresentare simbolicamente numeri
interi decimali (positivi e negativi); non avrebbe alcun senso quindi,
applicare a questo formato concetti come l'aritmetica modulare, il
complemento a 2, l'estensione del bit di segno, etc. Nel seguito
del capitolo, vedremo degli esempi pratici che chiariranno questo aspetto.
18.2 L'istruzione AAA
Con il mnemonico AAA si indica l'istruzione ASCII Adjust AL After
Addition (aggiustamento del contenuto di AL dopo una addizione
tra due cifre Unpacked BCD); lo scopo di questa istruzione è quello
di aggiustare il risultato di una somma tra due cifre codificate in formato
Unpacked BCD, in modo da farlo apparire formalmente identico a quello
che si ottiene in base 10.
In sostanza, AAA presuppone che il programmatore abbia appena eseguito
una istruzione ADD (o ADC) con operandi rappresentati da cifre
in formato Unpacked BCD; in tal caso, AAA legge il risultato
della somma e lo converte in formato Unpacked BCD.
L'unica forma lecita per l'istruzione AAA, è la seguente:
AAA
Come si può notare, il programmatore non deve specificare alcun operando
esplicito; infatti, la CPU assume che la somma si trovi nel registro
AL (operando SRC) e utilizza come operando DEST lo
stesso registro AL ed eventualmente anche il registro AH.
Per capire il principio di funzionamento di AAA, osserviamo innanzi
tutto che la somma tra due cifre decimali è sempre compresa tra il valore
minimo:
0 + 0 = 0
e il valore massimo:
9 + 9 = 18
L'istruzione AAA deve simulare gli stessi risultati che si ottengono
in base 10, compreso un eventuale riporto; per raggiungere questo
scopo, la CPU segue un preciso procedimento che viene analizzato
attraverso gli esempi seguenti.
- a) Il risultato è compreso tra 0 e 9 (cioè, tra
00h e 09h)
Poniamo AH=0, AL=2, BL=6 ed eseguiamo l'istruzione:
add al, bl
In presenza di questa istruzione, la CPU calcola:
AL = AL + BL = 2 + 6 = 00000010b + 00000110b = 00001000b = 08h = 8, con AF = 0
A questo punto, dobbiamo eseguire l'istruzione:
aaa
L'istruzione ADD ha prodotto AF=0 (nessun riporto dal nibble
basso al nibble alto), per cui la CPU capisce che il risultato
esadecimale è interamente contenuto nel nibble basso di AL.
La CPU azzera, in ogni caso, il nibble alto di AL e ottiene
AL=08h; questo valore non supera 09h, per cui non necessita di
alcun aggiustamento (infatti, il risultato esadecimale appare formalmente
identico a quello decimale).
La CPU lascia inalterato il contenuto di AH e segnala
l'assenza di riporto decimale ponendo AF=0, CF=0; alla fine
otteniamo il risultato Unpacked BCD dato da AH:AL=00h:08h.
- b) Il risultato è compreso tra 10 e 15 (cioè, tra
0Ah e 0Fh)
Poniamo AH=0, AL=5, BL=9 ed eseguiamo l'istruzione:
add al, bl
In presenza di questa istruzione, la CPU calcola:
AL = AL + BL = 5 + 9 = 00000101b + 00001001b = 00001110b = 0Eh = 14, con AF = 0
A questo punto, dobbiamo eseguire l'istruzione:
aaa
L'istruzione ADD ha prodotto AF=0 (nessun riporto dal nibble
basso al nibble alto), per cui la CPU capisce che il risultato
esadecimale è interamente contenuto nel nibble basso di AL.
La CPU azzera, in ogni caso, il nibble alto di AL e ottiene
AL=0Eh; questo valore supera 09h, per cui necessita di un
aggiustamento (infatti, il risultato esadecimale appare formalmente diverso
da quello decimale).
La CPU somma il valore 06h ad AL, e ottiene:
AL = AL + 06h = 0Eh + 06h = 14h
La CPU azzera di nuovo il nibble alto di AL, incrementa di
1 il contenuto di AH e segnala il riporto decimale ponendo
AF=1, CF=1; alla fine otteniamo il risultato Unpacked
BCD dato da AH:AL=01h:04h.
- b) Il risultato è compreso tra 16 e 18 (cioè, tra
10h e 12h)
Poniamo AH=0, AL=9, BL=9, ed eseguiamo l'istruzione:
add al, bl
In presenza di questa istruzione, la CPU calcola:
AL = AL + BL = 9 + 9 = 00001001b + 00001001b = 00010010b = 12h = 18, con AF = 1
A questo punto, dobbiamo eseguire l'istruzione:
aaa
L'istruzione ADD ha prodotto AF=1 (riporto dal nibble basso
al nibble alto), per cui la CPU capisce che il risultato esadecimale
occupa entrambi i nibble di AL; in questo caso, il risultato è
sicuramente superiore a 09h, per cui necessita di un aggiustamento
(infatti, il risultato esadecimale appare formalmente diverso da quello
decimale).
La CPU somma il valore 06h ad AL, e ottiene:
AL = AL + 06h = 12h + 06h = 18h
La CPU azzera il nibble alto di AL, incrementa di 1
il contenuto di AH e segnala il riporto decimale ponendo AF=1,
CF=1; alla fine otteniamo il risultato Unpacked BCD dato da
AH:AL=01h:08h.
Riassumendo, se AF=0 e CF=0, allora la somma in formato
Unpacked BCD è costituita unicamente dalla cifra contenuta nel
nibble basso di AL (nessun riporto decimale); se, invece, AF=1
e CF=1, allora la somma in formato Unpacked BCD è costituita
dalla cifra contenuta nel nibble basso di AL e da un riporto decimale
di 1.
Non è obbligatorio l'azzeramento di AH prima di una istruzione
AAA; il programmatore è libero di gestire come meglio crede il
contenuto di tale registro.
Ci si può chiedere che legame ci sia tra il codice
ASCII e i numeri in
formato Unpacked BCD; tale legame è dovuto ad un particolare effetto
collaterale prodotto dall'istruzione AAA.
Per chiarire questo aspetto, proviamo a calcolare la somma, in formato
Unpacked BCD, tra le due cifre 3 e 5; al loro posto,
utilizziamo però i codici
ASCII dei
corrispondenti simboli '3' e '5' e cioè, 33h e
35h.
Come si può notare, i nibble bassi dei due codici 33h e 35h,
valgono, rispettivamente, 3h e 5h, proprio come le due cifre
da sommare; eseguendo ora l'istruzione ADD con i due operandi
'3' e '5' (o, direttamente, 33h e 35h),
otteniamo:
AL = 33h + 35h = 68h, con AF = 0
In presenza ora di una istruzione AAA e in base a quanto abbiamo
visto in precedenza, la CPU azzera il nibble alto di AL,
lascia inalterato il contenuto di AH e segnala l'assenza di riporto
decimale ponendo AF=0, CF=0; alla fine otteniamo il risultato
Unpacked BCD dato da AH:AL=00h:08h (assumendo AH=0
prima dell'esecuzione di AAA).
Tale risultato è identico a quello che avremmo ottenuto sommando 3
e 5; in sostanza, l'istruzione AAA opera, indifferentemente,
su cifre Unpacked BCD o sui codici
ASCII dei simboli
corrispondenti alle cifre stesse!
18.2.1 Somma tra numeri Unpacked BCD di ampiezza arbitraria
Sulla base di quanto è stato esposto in precedenza, possiamo intuire che
la somma tra numeri Unpacked BCD di ampiezza arbitraria, può essere
svolta in modo semplicissimo; a tale proposito, vediamo subito un esempio
pratico.
Supponiamo di voler eseguire la seguente somma tra numeri interi decimali
da 16 cifre ciascuno:
Convertendo tutto in formato Unpacked BCD, dobbiamo ottenere:
Eseguendo la prima somma Unpacked BCD con ADD, si ha:
AL = 07h + 02h = 09h, con AF = 0
Una successiva istruzione AAA produce AL=09h, AF=0 e
CF=0.
Eseguendo la seconda somma Unpacked BCD con ADC, si ha:
AL = 09h + 04h + CF = 0Dh + 0h = 0Dh, con AF = 0
Una successiva istruzione AAA produce AL=03h, AF=1 e
CF=1.
Eseguendo la terza somma Unpacked BCD con ADC, si ha:
AL = 07h + 01h + CF = 08h + 1h = 09h, con AF = 0
Una successiva istruzione AAA produce AL=09h, AF=0 e
CF=0.
A questo punto è inutile continuare in quanto abbiamo capito che il
procedimento da seguire è del tutto simile a quello illustrato nel
precedente capitolo per l'istruzione ADC; l'unica differenza
sta nel fatto che, dopo ogni istruzione ADD (o ADC) con
operando AL, dobbiamo aggiungere una istruzione AAA.
Terminata l'ultima somma (tra le cifre più significative), dobbiamo
consultare CF; se CF=0, il risultato è valido, mentre
se CF=1, il risultato eccede le 16 cifre Unpacked
BCD.
Per definire i vari dati da elaborare, possiamo servirci della direttiva
DB (Define Byte) nel seguente modo:
È importante ricordare che, in questo tipo di definizioni, la cifra meno
significativa è quella più a sinistra.
Il codice che esegue la somma assume il seguente aspetto:
Dopo l'esecuzione dell'ultima somma (tra le due cifre più significative),
consultiamo CF per sapere se c'è stato un riporto finale; nel nostro
caso, CF=0, per cui il risultato è valido così com'è.
Se vogliamo visualizzare il risultato in formato Unpacked BCD, possiamo
servirci di writeHex8; partendo dalla cifra più significativa di
ubcdSomma, possiamo scrivere:
18.2.2 Effetti provocati da AAA sugli operandi e sui flags
Se la somma tra due cifre in formato Unpacked BCD è compresa tra
0 e 9, l'esecuzione dell'istruzione AAA provoca la
modifica del solo registro AL; se, invece, la somma tra due cifre
in formato Unpacked BCD è compresa tra 10 e 18,
l'esecuzione dell'istruzione AAA provoca la modifica dei registri
AL e AH.
L'esecuzione dell'istruzione AAA provoca la modifica dei campi
CF, PF, AF, ZF, SF e OF del Flags
Register; i campi PF, ZF, SF e OF, assumono un
valore indeterminato e non hanno quindi alcun significato.
La CPU pone AF=0 e CF=0, per segnalare che la somma tra
due cifre in formato Unpacked BCD è compresa tra 0 e 9;
la CPU pone, invece, AF=1 e CF=1, per segnalare che la
somma tra due cifre in formato Unpacked BCD è compresa tra 10
e 18 (riporto decimale).
18.3 L'istruzione AAS
Con il mnemonico AAS si indica l'istruzione ASCII Adjust AL After
Subtraction (aggiustamento del contenuto di AL dopo una sottrazione
tra due cifre Unpacked BCD); lo scopo di questa istruzione è quello
di aggiustare il risultato di una differenza tra due cifre codificate in
formato Unpacked BCD, in modo da farlo apparire formalmente identico a
quello che si ottiene in base 10.
In sostanza, AAS presuppone che il programmatore abbia appena eseguito
una istruzione SUB (o SBB) con operandi rappresentati da cifre
in formato Unpacked BCD; in tal caso, AAS legge il risultato
della sottrazione e lo converte in formato Unpacked BCD.
L'unica forma lecita per l'istruzione AAS, è la seguente:
AAS
Come si può notare, il programmatore non deve specificare alcun operando
esplicito; infatti, la CPU assume che la differenza si trovi nel
registro AL (operando SRC) e utilizza come operando
DEST lo stesso registro AL ed eventualmente anche il registro
AH.
Per capire il principio di funzionamento di AAS, osserviamo innanzi
tutto che, nel calcolo della differenza tra due cifre decimali, se il
minuendo è maggiore o uguale al sottraendo, si ottiene sempre
un risultato compreso tra 0 e 9, senza prestito dalla cifra
successiva del minuendo; se, invece, il minuendo è minore del
sottraendo, si ottiene sempre un risultato compreso tra 1 e
9, con prestito di 1 dalla cifra successiva del minuendo.
L'istruzione AAS deve simulare gli stessi risultati che si ottengono
in base 10, compreso un eventuale prestito; per raggiungere questo
scopo, la CPU segue un preciso procedimento che viene analizzato
attraverso gli esempi seguenti.
- a) Il minuendo è maggiore o uguale al sottraendo
Poniamo AH=0, AL=8, BL=3 ed eseguiamo l'istruzione:
sub al, bl
In presenza di questa istruzione, la CPU calcola:
AL = AL - BL = 8 - 3 = 00001000b - 00000011b = 00000101b = 05h = 5, con AF = 0
A questo punto, dobbiamo eseguire l'istruzione:
aas
L'istruzione SUB ha prodotto AF=0 (nessun prestito dal nibble
alto al nibble basso), per cui la CPU capisce che il risultato non
necessita di alcun aggiustamento (infatti, il risultato esadecimale appare
formalmente identico a quello decimale).
La CPU azzera, in ogni caso, il nibble alto di AL e ottiene
AL=05h.
La CPU lascia inalterato il contenuto di AH e segnala
l'assenza di prestito decimale ponendo AF=0, CF=0; alla fine
otteniamo il risultato Unpacked BCD dato da AH:AL=00h:05h.
- b) Il minuendo è minore del sottraendo
Poniamo AH=0, AL=5, BL=9, ed eseguiamo l'istruzione:
sub al, bl
In presenza di questa istruzione, la CPU calcola:
AL = AL - BL = 5 - 9 = 00000101b - 00001001b = 11111100b = FCh, con AF = 1
In base 10, la sottrazione 5-9 produce il risultato 6,
con prestito di 1.
A questo punto, dobbiamo eseguire l'istruzione:
aas
L'istruzione SUB ha prodotto AF=1 (prestito dal nibble alto
al nibble basso), per cui la CPU capisce che il risultato necessita
di un aggiustamento (infatti, il risultato esadecimale appare formalmente
diverso da quello decimale).
La CPU sottrae il valore 06h ad AL, e ottiene:
AL = AL - 06h = FCh - 06h = F6h
La CPU azzera il nibble alto di AL, decrementa di 1
il contenuto di AH e segnala il prestito decimale ponendo AF=1,
CF=1; alla fine otteniamo il risultato Unpacked BCD dato da
AH:AL=FFh:06h.
Riassumendo, se AF=0 e CF=0, allora la differenza in formato
Unpacked BCD è costituita unicamente dalla cifra contenuta nel
nibble basso di AL (nessun prestito decimale); se, invece, AF=1
e CF=1, allora la differenza in formato Unpacked BCD è
costituita dalla cifra contenuta nel nibble basso di AL e da un
prestito decimale di 1.
Non è obbligatorio l'azzeramento di AH prima di una istruzione
AAS; il programmatore è libero di gestire come meglio crede, il
contenuto di tale registro.
Anche per AAS, vale l'effetto collaterale già descritto per
AAA; in sostanza, l'istruzione AAS opera, indifferentemente,
su cifre Unpacked BCD o sui codici
ASCII dei simboli
corrispondenti alle cifre stesse!
18.3.1 Differenza tra numeri Unpacked BCD di ampiezza arbitraria
Sulla base di quanto è stato esposto in precedenza, possiamo intuire che
la differenza tra numeri Unpacked BCD di ampiezza arbitraria, può
essere svolta in modo semplicissimo; a tale proposito, vediamo subito un
esempio pratico.
Supponiamo di voler eseguire la seguente differenza tra numeri interi decimali
da 20 cifre ciascuno:
Convertendo tutto in formato Unpacked BCD, dobbiamo ottenere:
Eseguendo la prima differenza Unpacked BCD con SUB, si ha:
AL = 02h - 01h = 01h, con AF = 0
Una successiva istruzione AAS produce AL=01h, AF=0 e
CF=0.
Eseguendo la seconda differenza Unpacked BCD con SBB, si ha:
AL = 04h - 07h - CF = FDh - 0h = FDh, con AF = 1
Una successiva istruzione AAS produce AL=07h, AF=1 e
CF=1.
Eseguendo la terza differenza Unpacked BCD con SBB, si ha:
AL = 09h - 08h - CF = 01h - 1h = 00h, con AF = 0
Una successiva istruzione AAS produce AL=00h, AF=0 e
CF=0.
A questo punto è inutile continuare in quanto abbiamo capito che il
procedimento da seguire, è del tutto simile a quello illustrato nel
precedente capitolo per l'istruzione SBB; l'unica differenza
sta nel fatto che, dopo ogni istruzione SUB (o SBB) con
operando AL, dobbiamo aggiungere una istruzione AAS.
Terminata l'ultima differenza (tra le cifre più significative), dobbiamo
consultare CF; se CF=0, il risultato è valido, mentre
se CF=1, si è verificato un prestito finale (minuendo
minore del sottraendo).
Come è stato già detto, la codifica BCD ha il solo scopo di
permettere una rappresentazione simbolica dei numeri interi decimali;
di conseguenza, il programmatore ha il compito di gestire le varie
situazioni che si possono presentare in relazione al segno degli operandi.
Ad esempio, prima di eseguire una sottrazione il cui minuendo è
minore del sottraendo, dobbiamo scambiare di posto i due operandi,
in modo da ottenere un risultato positivo; a questo risultato, dobbiamo
poi assegnare il segno negativo. Naturalmente, è nostro compito definire
anche il metodo per la codifica del segno; prendendo spunto, ad esempio,
dai numeri Packed BCD standard, possiamo utilizzare la cifra più
significativa di un numero Unpacked BCD (di ampiezza definita),
per memorizzare il segno del numero stesso.
Se vogliamo implementare in pratica l'esempio precedente, possiamo procedere
con la definizione dei dati nel seguente modo:
Il codice che esegue la differenza assume il seguente aspetto:
Dopo l'esecuzione dell'ultima differenza (tra le due cifre più significative),
consultiamo CF per sapere se c'è stato un prestito finale; nel nostro
caso, CF=0, per cui il risultato è valido così com'è.
Se vogliamo visualizzare il risultato in formato Unpacked BCD, possiamo
procedere come nell'esempio su AAA; in questo caso, l'output inizia da
[ubcdDiff+19].
18.3.2 Effetti provocati da AAS sugli operandi e sui flags
Se la differenza tra due cifre in formato Unpacked BCD non richiede
alcun prestito, l'esecuzione dell'istruzione AAS provoca la modifica
del solo registro AL; se, invece, la differenza tra due cifre in
formato Unpacked BCD richiede un prestito, l'esecuzione dell'istruzione
AAS provoca la modifica dei registri AL e AH.
L'esecuzione dell'istruzione AAS provoca la modifica dei campi
CF, PF, AF, ZF, SF e OF del Flags
Register; i campi PF, ZF, SF e OF, assumono un
valore indeterminato e non hanno quindi alcun significato.
La CPU pone AF=0 e CF=0, per segnalare che la differenza
tra due cifre in formato Unpacked BCD non ha richiesto alcun prestito;
la CPU pone, invece, AF=1 e CF=1, per segnalare che la
differenza tra due cifre in formato Unpacked BCD ha richiesto un
prestito.
18.4 L'istruzione AAM
Con il mnemonico AAM si indica l'istruzione ASCII Adjust AX After
Multiply (aggiustamento del contenuto di AX dopo una moltiplicazione
tra due cifre Unpacked BCD); lo scopo di questa istruzione è quello di
aggiustare il risultato di un prodotto tra due cifre codificate in formato
Unpacked BCD, in modo da farlo apparire formalmente identico a quello
che si ottiene in base 10.
In sostanza, AAM presuppone che il programmatore abbia appena
eseguito una istruzione MUL con operandi rappresentati da cifre in
formato Unpacked BCD; in tal caso, AAM legge il risultato
della moltiplicazione e lo converte in formato Unpacked BCD.
In virtù del fatto che i numeri BCD non seguono le leggi
dell'aritmetica modulare, è necessario sottolineare che AAM produce
un risultato valido solo se viene eseguita subito dopo una istruzione
MUL (prodotto tra numeri interi senza segno); se, invece, si esegue
AAM dopo una istruzione IMUL, si ottengono risultati privi di
senso!
L'unica forma lecita per l'istruzione AAM è la seguente:
AAM
Come si può notare, il programmatore non deve specificare alcun operando
esplicito; infatti, la CPU assume che il prodotto si trovi nel
registro AX (operando SRC) e utilizza come operando
DEST lo stesso registro AX.
Per capire il principio di funzionamento di AAM, osserviamo innanzi
tutto che il prodotto tra due cifre decimali è un numero formato, al
massimo, da due cifre. Tale prodotto, è sempre compreso tra il valore
minimo:
0 * 0 = 0
e il valore massimo:
9 * 9 = 81
Per convertire il risultato decimale in formato Unpacked BCD,
l'istruzione AAM si serve del metodo delle divisioni successive,
già illustrato nel Capitolo 4; tale metodo prevede che, dividendo
ripetutamente un numero intero in base b1, per una qualsiasi base
b2 (divisione intera), si ottengono una serie di resti che
rappresentano la sequenza delle cifre in base b2 del numero stesso. Le
divisioni terminano quando si ottiene un quoziente nullo; l'ultimo resto
ottenuto rappresenta la cifra più significativa del numero appena
convertito in base b2.
Applichiamo ora questo metodo ad un numero intero decimale a due sole cifre,
da dividere per la sua stessa base b=10; a tale proposito, consideriamo
il seguente prodotto decimale:
6 * 9 = 54
Dividendo ora 54 per la sua base 10 (divisione intera), si
ottiene:
54 / 10, Q = 5, R = 4
Come possiamo notare, il quoziente e il resto rappresentano,
rispettivamente, la cifra più significativa e quella meno significativa di
54!
Una volta che il numero è stato scomposto nelle sue due cifre, è facile
codificarlo in formato Unpacked BCD; vediamo subito alcuni esempi
pratici.
Poniamo AL=3, BL=2 ed eseguiamo l'istruzione:
mul bl
In presenza di questa istruzione, la CPU calcola:
AH:AL = AL * BL = 3 * 2 = 6
A questo punto, dobbiamo eseguire l'istruzione:
aam
La CPU divide il prodotto 6 per la sua base 10
e ottiene:
Temp8:Temp8 = AH:AL / 10 = 6 / 10, Q = 0, R = 6
Successivamente, la CPU pone AL=R=06h e AH=Q=00h; alla
fine otteniamo il risultato Unpacked BCD dato da AH:AL=00h:06h.
Poniamo AL=9, BL=6 ed eseguiamo l'istruzione:
mul bl
In presenza di questa istruzione, la CPU calcola:
AH:AL = AL * BL = 9 * 6 = 54
A questo punto, dobbiamo eseguire l'istruzione:
aam
La CPU divide il prodotto 54 per la sua base 10
e ottiene:
Temp8:Temp8 = AH:AL / 10 = 54 / 10, Q = 5, R = 4
Successivamente, la CPU pone AL=R=04h e AH=Q=05h; alla
fine otteniamo il risultato Unpacked BCD dato da AH:AL=05h:04h.
A differenza di quanto accade per AAA e AAS, l'istruzione
AAM opera, esclusivamente, su cifre in formato Unpacked BCD;
non è possibile quindi utilizzare i codici
ASCII dei simboli
corrispondenti alle cifre stesse!
18.4.1 Codifica binaria di numeri interi non decimali
L'istruzione AAM si serve della base predefinita 10; esiste
però la possibilità di codificare in binario, numeri espressi in
qualsiasi altra base. A tale proposito, è necessario servirsi,
obbligatoriamente, di un apposito codice macchina rappresentato dalla
sequenza:
11010100b Imm8 = D4h Imm8
Il valore immediato Imm8 rappresenta la base da utilizzare (ad
esempio, 8, 8h o 10q per la notazione ottale);
ovviamente, l'utilizzo dell'Opcode D4h con base 0, provoca
un overflow di divisione!
Supponiamo, ad esempio, di voler codificare in binario scompattato, numeri
espressi in base 8; in tal caso, potremmo parlare di formato
Unpacked BCO (Binary Coded Octal). In particolare, consideriamo
il seguente prodotto:
3 * 7 = 21 = 25q
(è ovvio che, in notazione ottale, il prodotto deve svolgersi tra cifre
comprese tra 0 e 7).
Se ora vogliamo convertire il risultato in formato Unpacked BCO,
possiamo scrivere le seguenti istruzioni:
Provando ora a visualizzare il contenuto dei due registri AH e
AL, otteniamo proprio AH:AL=02h:05h!
18.4.2 Prodotto tra numeri Unpacked BCD di ampiezza arbitraria
Sulla base di quanto è stato esposto in precedenza, possiamo intuire che
il prodotto tra numeri Unpacked BCD di ampiezza arbitraria, può
essere svolto in modo semplicissimo; a tale proposito, vediamo subito un
esempio pratico.
Supponiamo di voler eseguire il seguente prodotto:
6973 * 8 = 55784
Moltiplicando un numero a 4 cifre per un numero a 1 cifra,
otteniamo un risultato che richiede, al massimo, 4+1=5 cifre.
Convertendo tutto in formato Unpacked BCD, dobbiamo ottenere:
06090703h * 08h = 0505070804h
Moltiplicando un numero da 4 BYTE per un numero da 1 BYTE,
otteniamo un risultato che richiede, al massimo, 4+1=5 BYTE.
Eseguendo il primo prodotto Unpacked BCD con MUL, si ha:
AH:AL = 03h * 08h = 18h = 24
Una successiva istruzione AAM produce AH:AL=02h:04h, cioè,
04h con riporto di 02h.
Eseguendo il secondo prodotto Unpacked BCD con MUL, si ha:
AH:AL = 07h * 08h = 38h = 56
Una successiva istruzione AAM produce AH:AL=05h:06h, cioè,
06h con riporto di 05h.
Alla cifra 06h dobbiamo sommare il precedente riporto 02h;
a tale proposito, servendoci dell'istruzione ADD, otteniamo:
AL = 06h + 02h = 08h, con AF = 0
Una successiva istruzione AAA produce AL=08h, con
AF=0 e CF=0.
La precedente somma non provoca un carry, per cui il riporto
05h (incrementato con ADC) rimane invariato.
Eseguendo il terzo prodotto Unpacked BCD con MUL, si ha:
AH:AL = 09h * 08h = 48h = 72
Una successiva istruzione AAM produce AH:AL=07h:02h, cioè,
02h con riporto di 07h.
Alla cifra 02h dobbiamo sommare il precedente riporto 05h;
a tale proposito, servendoci dell'istruzione ADD, otteniamo:
AL = 02h + 05h = 07h, con AF = 0
Una successiva istruzione AAA produce AL=07h, con
AF=0 e CF=0.
La precedente somma non provoca un carry, per cui il riporto
07h (incrementato con ADC) rimane invariato.
Eseguendo il quarto prodotto Unpacked BCD con MUL, si ha:
AH:AL = 06h * 08h = 30h = 48
Una successiva istruzione AAM produce AH:AL=04h:08h, cioè,
08h con riporto di 04h.
Alla cifra 08h dobbiamo sommare il precedente riporto 07h;
a tale proposito, servendoci dell'istruzione ADD, otteniamo:
AL = 08h + 07h = 0Fh, con AF = 0
Una successiva istruzione AAA produce AL=05h, con
AF=1 e CF=1.
L'ultimo riporto 04h (incrementato con ADC) diventa
05h e rappresenta la cifra più significativa del risultato.
Unendo le 5 cifre appena calcolate, otteniamo proprio
0505070804h!.
Come possiamo notare, il procedimento da seguire è del tutto simile a
quello utilizzato nel precedente capitolo per l'istruzione MUL;
il codice è molto più lungo a causa del fatto che dobbiamo operare
sulle singole cifre Unpacked BCD.
Il procedimento appena illustrato, può essere notevolmente semplificato
eliminando tutte le istruzioni AAA e ADC e posizionando
AAM dopo ADD; infatti, osserviamo innanzi tutto che AAM
opera correttamente su un qualsiasi prodotto compreso tra 0 e
99. Il prodotto massimo che possiamo ottenere tra due cifre decimali è:
9 * 9 = 81 (cioè, 1 con riporto di 8)
Se il precedente riporto era 9, cioè il massimo possibile,
l'istruzione ADD produce:
81 + 9 = 90 (cioè, 0 con riporto di 9)
Tale risultato è chiaramente inferiore a 99; a questo punto, una
istruzione AAM produce:
AH:AL = 09h:00h (cioè, 0 con riporto di 9)
Se vogliamo implementare in pratica l'esempio precedente, possiamo procedere
con la definizione dei dati nel seguente modo:
In questo caso particolare, il dato ubcdFatt1 può essere definito
anche come:
ubcdFatt1 dd 06090703h
Il codice che esegue il prodotto assume il seguente aspetto:
Se vogliamo visualizzare il risultato in formato Unpacked BCD, possiamo
procedere come nell'esempio sull'istruzione AAA; in questo caso, l'output
inizia da [ubcdProd+4].
Cosa succede se anche il moltiplicatore è formato da due o più
cifre decimali?
Anche in un caso del genere dobbiamo ricorrere allo stesso algoritmo che
si segue con carta e penna; il codice da scrivere diventa notevolmente
più lungo, principalmente a causa del fatto che, i vari prodotti parziali,
devono essere sommati tra loro sempre in formato Unpacked BCD.
Consideriamo, ad esempio, il seguente prodotto:
Moltiplicando un numero a 8 cifre per un numero a 3 cifre,
otteniamo un prodotto che richiede, al massimo, 8+3=11 cifre;
traducendo tutto in formato Unpacked BCD, dobbiamo ottenere:
Moltiplicando un numero da 8 BYTE per un numero da 3 BYTE,
otteniamo un prodotto che richiede, al massimo, 8+3=11 BYTE.
Applicando ora il metodo illustrato nel precedente esempio, otteniamo tre
prodotti parziali (uno per ogni cifra del moltiplicatore); la somma,
in formato Unpacked BCD, tra i primi due prodotti parziali, produce:
La somma, in formato Unpacked BCD, tra il risultato appena ottenuto
e il terzo prodotto parziale, produce:
Analizzando la struttura delle somme appena svolte, si può notare che
per far scorrere verso sinistra i BYTE dei vari prodotti parziali,
non abbiamo la necessità di ricorrere alle istruzioni di shifting; tutto
ciò è una diretta conseguenza del fatto che i numeri Unpacked BCD,
sono suddivisi appunto in BYTE e quindi sono facilmente
maneggiabili anche senza l'ausilio delle istruzioni logiche (illustrate
nel capitolo successivo).
Un'ultima considerazione riguarda l'eventualità di dover moltiplicare tra
loro, numeri Unpacked BCD con segno.
Come è stato già sottolineato, tutte le operazioni in formato Unpacked
BCD devono svolgersi tra numeri senza segno; alla fine, si deve procedere
alla opportuna codifica del segno dei risultati che scaturiscono dalle
operazioni stesse.
18.4.3 Effetti provocati da AAM sugli operandi e sui flags
L'esecuzione dell'istruzione AAM provoca la modifica del solo registro
AX.
L'esecuzione dell'istruzione AAM provoca la modifica dei campi
CF, PF, AF, ZF, SF e OF del Flags
Register; i campi CF, AF, OF, assumono un valore
indeterminato e non hanno quindi alcun significato logico.
I campi PF, ZF e SF forniscono, invece, informazioni
ordinarie relative al numero binario memorizzato in AL da AAM;
in sostanza, PF indica se AL contiene un numero pari o dispari
di bit a livello logico 1, ZF indica se il contenuto di
AL vale zero, SF indica se il bit più significativo di
AL vale 1. Generalmente, il contenuto di questi tre flags viene
ignorato.
18.5 L'istruzione AAD
Con il mnemonico AAD si indica l'istruzione ASCII Adjust AX Before
Division (aggiustamento del contenuto di AX prima di una divisione);
lo scopo di questa istruzione è quello di predisporre un dividendo
formato da due cifre Unpacked BCD, affinché una successiva divisione
con un divisore formato da una cifra Unpacked BCD, produca un
risultato formalmente identico a quello che si ottiene in base 10.
In sostanza, AAD presuppone che il programmatore stia per eseguire
una istruzione DIV tra un dividendo compreso tra 00h:00h
e 09h:09h e un divisore compreso tra 01h e 09h;
in tal caso, AAD modifica la struttura del dividendo, in modo
che DIV fornisca un quoziente e un resto, entrambi in
formato Unpacked BCD.
In virtù del fatto che i numeri BCD non seguono le leggi
dell'aritmetica modulare, è necessario sottolineare che AAD produce
un risultato valido solo se viene eseguita prima di una istruzione DIV
(divisione tra numeri interi senza segno); se, invece, si esegue AAD
prima di una istruzione IDIV, si ottengono risultati privi di senso!
È importante ribadire che, a differenza di quanto accade con AAA,
AAS e AAM, l'istruzione AAD deve essere eseguita prima
e non dopo l'operazione da effettuare (che in questo caso è DIV)!
L'unica forma lecita per l'istruzione AAD, è la seguente:
AAD
Come si può notare, il programmatore non deve specificare alcun operando
esplicito; infatti, la CPU assume che il dividendo si trovi
nel registro AX (operando SRC) e utilizza come operando
DEST lo stesso registro AX (quoziente in AL
e resto in AH).
Per svolgere il proprio lavoro, l'istruzione AAD non fa altro che
compattare in un solo BYTE, le due cifre Unpacked BCD del
dividendo; a tale proposito, viene utilizzata una nota proprietà
dei sistemi di numerazione posizionale. Nel caso della base 10
possiamo scrivere, ad esempio:
35 = (3 * 101) + (5 * 100) = (3 * 10) + 5
Traducendo tutto in formato Unpacked BCD, otteniamo:
03h:05h = (03h * 10) + 05h = (03h * 0Ah) + 05h = 1Eh + 05h = 23h = 35
Una volta che il dividendo è stato compattato, possiamo dividerlo
per un divisore formato da una sola cifra Unpacked BCD,
ottenendo così un quoziente e un resto, entrambi in
formato Unpacked BCD; vediamo subito alcuni esempi pratici.
Vogliamo calcolare:
58 / 7, Q = 8, R = 2
Poniamo AX=AH:AL=05h:08h ed eseguiamo l'istruzione:
aad
In presenza di questa istruzione, la CPU calcola:
Temp8 = (AH * 10) + AL = (05h * Ah) + 08h = 32h + 08h = 3Ah = 58
La CPU carica il valore Temp8=58=3Ah in AL e azzera
il contenuto di AH in modo da ottenere AH:AL=00h:3Ah; a
questo punto, poniamo BL=07h ed eseguiamo l'istruzione:
div bl
In presenza di questa istruzione, la CPU calcola:
AH:AL = AH:AL / 7 = 58 / 7 = 02h:08h
Vogliamo calcolare:
35 / 5, Q = 7, R = 0
Poniamo AX=AH:AL=03h:05h, ed eseguiamo l'istruzione:
aad
In presenza di questa istruzione, la CPU calcola:
Temp8 = (AH * 10) + AL = (03h * Ah) + 05h = 1Eh + 05h = 23h = 35
La CPU carica il valore Temp8=35=23h in AL e azzera
il contenuto di AH in modo da ottenere AH:AL=00h:23h; a
questo punto, poniamo BL=05h ed eseguiamo l'istruzione:
div bl
In presenza di questa istruzione, la CPU calcola:
AH:AL = AH:AL / 5 = 35 / 5 = 00h:07h
A differenza di quanto accade per AAA e AAS, l'istruzione
AAD opera, esclusivamente, su cifre in formato Unpacked BCD;
non è possibile quindi utilizzare i codici
ASCII dei simboli
corrispondenti alle cifre stesse!
18.5.1 Codifica binaria di numeri interi non decimali
L'istruzione AAD si serve della base predefinita 10; esiste
però la possibilità di codificare in binario, numeri espressi in
qualsiasi altra base. A tale proposito, è necessario servirsi,
obbligatoriamente, di un apposito codice macchina rappresentato dalla
sequenza:
11010101b Imm8 = D5h Imm8
Il valore immediato Imm8 rappresenta la base da utilizzare (ad
esempio, 8, 8h o 10q per la notazione ottale);
utilizzando, ad esempio, Imm8=8, otteniamo un quoziente
e un resto, entrambi in formato Unpacked Binary Coded Octal.
18.5.2 Divisione tra numeri Unpacked BCD di ampiezza arbitraria
Sulla base di quanto è stato esposto in precedenza, possiamo intuire che
la divisione tra numeri Unpacked BCD di ampiezza arbitraria, può
essere svolta in modo semplicissimo; a tale proposito, vediamo subito un
esempio pratico.
Supponiamo di voler eseguire la seguente divisione:
3578 / 9, (Q = 397, R = 5)
Dividendo un numero a 4 cifre per un numero a 1 cifra,
otteniamo un quoziente che richiede, al massimo, 4 cifre
e un resto che richiede, al massimo, 1 cifra.
Convertendo tutto in formato Unpacked BCD, dobbiamo ottenere:
03050708h / 09h, (Q = 00030907h, R = 05h)
Dividendo un numero da 4 BYTE per un numero da 1 BYTE,
otteniamo un quoziente che richiede, al massimo, 4 BYTE
e un resto che richiede, al massimo, 1 BYTE.
Come al solito, la divisione inizia con la cifra più significativa del
dividendo; nel nostro caso quindi, il primo quoziente parziale da
calcolare, in formato Unpacked BCD, è:
00h:03h / 09h, (Q = 00h, R = 03h)
Una istruzione AAD produce:
AH:AL = 00h:03h
Una successiva istruzione DIV con divisore 09h, produce:
Q3 = AL = 00h, R3 = AH = 03h
Il quoziente parziale indicato con Q3 rappresenta la cifra più
significativa del quoziente finale; al resto parziale R3
dobbiamo affiancare la seconda cifra (da sinistra) del dividendo,
in modo da ottenere 03h:05h.
Il secondo quoziente parziale da calcolare, in formato Unpacked BCD, è:
03h:05h / 9h, (Q = 03h, R = 08h)
Una istruzione AAD produce:
AH:AL = 00h:23h
Una successiva istruzione DIV con divisore 09h, produce:
Q2 = AL = 03h, R2 = AH = 08h
Il quoziente parziale indicato con Q2 rappresenta la seconda cifra
(da sinistra) del quoziente finale; al resto parziale R2
dobbiamo affiancare la terza cifra (da sinistra) del dividendo, in
modo da ottenere 08h:07h.
Il terzo quoziente parziale da calcolare, in formato Unpacked BCD, è:
08h:07h / 9h, (Q = 09h, R = 06h)
Una istruzione AAD produce:
AH:AL = 00h:57h
Una successiva istruzione DIV con divisore 09h, produce:
Q1 = AL = 09h, R1 = AH = 06h
Il quoziente parziale indicato con Q1 rappresenta la terza cifra
(da sinistra) del quoziente finale; al resto parziale R1
dobbiamo affiancare la quarta cifra (da sinistra) del dividendo, in
modo da ottenere 06h:08h.
Il quarto e ultimo quoziente parziale da calcolare, in formato Unpacked
BCD, è:
06h:08h / 9h, (Q = 07h, R = 05h)
Una istruzione AAD produce:
AH:AL = 00h:44h
Una successiva istruzione DIV con divisore 09h, produce:
Q0 = AL = 07h, R0 = AH = 05h
Il quoziente parziale indicato con Q0 rappresenta cifra meno
significativa del quoziente finale; il resto parziale R0
rappresenta il resto finale della divisione.
Unendo i 4 quozienti parziali otteniamo il quoziente finale
00030907h; inoltre, abbiamo appena visto che l'ultimo resto parziale
R0, ci fornisce il resto finale 05h.
Come possiamo notare, il procedimento da seguire è del tutto simile a
quello utilizzato nel precedente capitolo per l'istruzione DIV;
come al solito, il codice è molto più lungo a causa del fatto che dobbiamo
operare sulle singole cifre Unpacked BCD.
Se vogliamo implementare in pratica l'esempio precedente, possiamo procedere
con la definizione dei dati nel seguente modo:
In questo caso particolare, il dato ubcdDividendo può essere definito
anche come:
ubcdDividendo dd 03050708h
Il codice che esegue la divisione assume il seguente aspetto:
Come al solito, è fondamentale che nella prima divisione, il contenuto di
AH venga rigorosamente azzerato!
Se vogliamo visualizzare il risultato in formato Unpacked BCD, possiamo
procedere come nell'esempio su AAA; in questo caso, l'output del
quoziente inizia da [ubcdQuoziente+3].
Cosa succede se anche il divisore è formato da due o più cifre
decimali?
Anche in un caso del genere dobbiamo ricorrere allo stesso algoritmo che
si segue con carta e penna; il codice da scrivere diventa però notevolmente
complesso!
In situazioni del genere, può diventare conveniente operare una conversione
da BCD a binario, eseguire i calcoli in binario e riconvertire infine
i risultati da binario a BCD; più avanti verranno illustrati ulteriori
dettagli su questo aspetto.
In relazione all'eventualità di dover dividere tra loro numeri Unpacked
BCD con segno, valgono le stesse considerazioni già svolte per AAM.
18.5.3 Effetti provocati da AAD sugli operandi e sui flags
L'esecuzione dell'istruzione AAD provoca la modifica del solo registro
AX.
L'esecuzione dell'istruzione AAD provoca la modifica dei campi
CF, PF, AF, ZF, SF e OF del Flags
Register; i campi CF, AF, OF, assumono un valore
indeterminato e non hanno quindi alcun significato logico.
I campi PF, ZF e SF forniscono, invece, informazioni
ordinarie relative al numero binario memorizzato in AL da AAD;
in sostanza, PF indica se AL contiene un numero pari o dispari
di bit a livello logico 1, ZF indica se il contenuto di
AL vale zero, SF indica se il bit più significativo di
AL vale 1. Generalmente, anche il contenuto di questi tre
flags viene ignorato.
Ovviamente, in relazione all'istruzione DIV da eseguire dopo AAD,
valgono tutte le considerazioni esposte nel precedente capitolo; in presenza
quindi di un dividendo troppo grande e/o di un divisore nullo,
si verifica un overflow di divisione!
18.6 L'istruzione DAA
Con il mnemonico DAA si indica l'istruzione Decimal Adjust AL after
Addition (aggiustamento del contenuto di AL dopo una addizione
tra due numeri Packed BCD a 8 bit); lo scopo di questa istruzione
è quello di aggiustare il risultato di una somma tra due numeri Packed
BCD a 8 bit, in modo da farlo apparire formalmente identico a quello
che si ottiene in base 10.
In sostanza, DAA presuppone che il programmatore abbia appena eseguito
una istruzione ADD (o ADC) tra due numeri Packed BCD a
8 bit; in tal caso, DAA legge il risultato della somma e lo
converte in formato Packed BCD a 8 bit.
L'unica forma lecita per l'istruzione DAA, è la seguente:
DAA
Come si può notare, il programmatore non deve specificare alcun operando
esplicito; infatti, la CPU assume che la somma si trovi nel registro
AL (operando SRC) e utilizza come operando DEST lo
stesso registro AL.
L'istruzione DAA deve simulare, in formato Packed BCD, gli
identici risultati che si ottengono sommando due numeri interi decimali,
ciascuno compreso tra 0 e 99; di conseguenza, DAA
deve anche simulare un eventuale riporto dal nibble basso al nibble alto
(AF) e un eventuale riporto al byte successivo (CF).
Per capire il principio di funzionamento di DAA, osserviamo innanzi
tutto che la somma tra numeri decimali a due cifre, è sempre compresa tra
il valore minimo:
0 + 0 = 0
e il valore massimo:
99 + 99 = 198
(cioè, 98 con riporto di 1).
Si possono presentare allora diverse possibilità, che vengono segnalate
dalla CPU attraverso la modifica (separata) dei flags AF e
CF; a tale proposito, analizziamo alcuni esempi pratici.
Vogliamo calcolare:
2 + 6 = 8
Poniamo AL=02h, BL=06h, ed eseguiamo l'istruzione:
add al, bl
In presenza di questa istruzione, la CPU calcola:
AL = AL + BL = 02h + 06h = 00000010b + 00000110b = 00001000b = 08h, con AF = 0, CF = 0
A questo punto, dobbiamo eseguire l'istruzione:
daa
L'istruzione ADD ha prodotto AF=0 (nessun riporto dal nibble
basso al nibble alto) e inoltre, la cifra 8h contenuta nel nibble
basso di AL, non supera 9h; di conseguenza, tale cifra non
necessita di alcun aggiustamento (AF e CF rimangono a
0).
I calcoli precedenti hanno prodotto CF=0 (nessun riporto al byte
successivo) e inoltre, la cifra 0h contenuta nel nibble alto di
AL, non supera 9h; di conseguenza, tale cifra non necessita
di alcun aggiustamento (CF rimane a 0).
Le elaborazioni eseguite dalla CPU hanno prodotto AF=0 e
CF=0; alla fine otteniamo il risultato Packed BCD dato da
AL=08h.
Vogliamo calcolare:
5 + 9 = 14
Poniamo AL=05h, BL=09h, ed eseguiamo l'istruzione:
add al, bl
In presenza di questa istruzione, la CPU calcola:
AL = AL + BL = 05h + 09h = 00000101b + 00001001b = 00001110b = 0Eh, con AF = 0, CF = 0
A questo punto, dobbiamo eseguire l'istruzione:
daa
L'istruzione ADD ha prodotto AF=0 (nessun riporto dal nibble
basso al nibble alto), ma la cifra Eh contenuta nel nibble basso di
AL, supera 9h; di conseguenza, tale cifra necessita di un
opportuno aggiustamento.
La CPU somma 06h al contenuto di AL e ottiene:
AL = AL + 06h = 0Eh + 06h = 14h, con TempCF = 0
Inoltre, la CPU pone:
AF = 1
e
CF = CF OR TempCF = 0 OR 0 = 0
I calcoli precedenti hanno prodotto CF=0 (nessun riporto al byte
successivo) e inoltre, la cifra 1h contenuta nel nibble alto di
AL, non supera 9h; di conseguenza, tale cifra non necessita
di alcun aggiustamento (CF rimane a 0).
Le elaborazioni eseguite dalla CPU hanno prodotto AF=1 e
CF=0; alla fine otteniamo il risultato Packed BCD dato da
AL=14h.
Vogliamo calcolare:
95 + 10 = 105
Poniamo AL=95h, BL=10h, ed eseguiamo l'istruzione:
add al, bl
In presenza di questa istruzione, la CPU calcola:
AL = AL + BL = 95h + 10h = 10010101b + 00010000b = 10100101b = A5h, con AF = 0, CF = 0
A questo punto, dobbiamo eseguire l'istruzione:
daa
L'istruzione ADD ha prodotto AF=0 (nessun riporto dal nibble
basso al nibble alto) e inoltre, la cifra 5h contenuta nel nibble
basso di AL, non supera 9h; di conseguenza, tale cifra non
necessita di alcun aggiustamento (AF e CF rimangono a
0).
I calcoli precedenti hanno prodotto CF=0 (nessun riporto al byte
successivo), ma la cifra Ah contenuta nel nibble alto di AL,
supera 9h; di conseguenza, tale cifra necessita di un aggiustamento.
La CPU somma 60h al contenuto di AL e ottiene:
AL = AL + 60h = A5h + 60h = 05h
Inoltre, la CPU pone:
CF = 1
Le elaborazioni eseguite dalla CPU hanno prodotto AF=0 e
CF=1; alla fine otteniamo il risultato Packed BCD dato da
AL=05h.
Vogliamo calcolare:
89 + 79 = 168
Poniamo AL=89h, BL=79h ed eseguiamo l'istruzione:
add al, bl
In presenza di questa istruzione, la CPU calcola:
AL = AL + BL = 89h + 79h = 10001001b + 01111001b = 00000010b = 02h, con AF = 1, CF = 1
A questo punto, dobbiamo eseguire l'istruzione:
daa
L'istruzione ADD ha prodotto AF=1 (riporto dal nibble basso
al nibble alto); di conseguenza, la cifra contenuta nel nibble basso di
AL necessita sicuramente di un aggiustamento.
La CPU somma 06h al contenuto di AL e ottiene:
AL = AL + 06h = 02h + 06h = 08h, con TempCF = 0
Inoltre, la CPU pone:
AF = 1
e
CF = CF OR TempCF = 1 OR 0 = 1
I calcoli precedenti hanno prodotto CF=1 (riporto al byte successivo);
di conseguenza, la cifra contenuta nel nibble alto di AL necessita
sicuramente di un aggiustamento.
La CPU somma 60h al contenuto di AL e ottiene:
AL = AL + 60h = 08h + 60h = 68h
Inoltre, la CPU pone:
CF = 1
Le elaborazioni eseguite dalla CPU hanno prodotto AF=1 e
CF=1; alla fine otteniamo il risultato Packed BCD dato da
AL=68h.
Riassumendo, la CPU utilizza AF per indicarci se la somma
Packed BCD ha prodotto un riporto dal nibble basso al nibble
alto; analogamente, la CPU utilizza CF per indicarci se
la somma Packed BCD ha prodotto un riporto verso un ipotetico
gruppo successivo di cifre Packed BCD. Il risultato della somma,
memorizzato in AL, è identico a quello che si ottiene in base
10; inoltre, attraverso CF, possiamo sommare numeri
Packed BCD di ampiezza arbitraria.
18.6.1 Somma tra numeri Packed BCD di ampiezza arbitraria
Sulla base di quanto è stato esposto in precedenza, possiamo intuire che
la somma tra numeri Packed BCD di ampiezza arbitraria, può essere
svolta in modo ancora più semplice rispetto al caso dei numeri Unpacked
BCD; in relazione all'esempio presentato per l'istruzione AAA,
le uniche differenze sono le seguenti:
- con i numeri Packed BCD operiamo su due cifre per volta
- al posto dell'istruzione AAA dobbiamo utilizzare DAA
Vediamo subito un esempio pratico che ci permette di illustrare l'utilizzo
dei numeri Packed BCD standard; a tale proposito, supponiamo di voler
eseguire la seguente somma tra numeri interi decimali da 18 cifre
ciascuno:
Convertendo tutto in formato Packed BCD standard, dobbiamo ottenere:
Ricordiamo che, il segno di un numero Packed BCD standard, viene
codificato nel BYTE più significativo; il codice 00h
rappresenta il segno positivo, mentre il codice 80h rappresenta
il segno negativo.
Bisogna anche ricordare che con le direttive DQ e DT, il
NASM accetta solo numeri reali come valori inizializzanti; non è
possibile quindi utilizzare DT per definire numeri codificati in
formato Packed BCD.
Possiamo procedere allora nel seguente modo:
Il codice che esegue la somma assume il seguente aspetto:
Dopo l'esecuzione dell'ultima somma (tra gli ottavi BYTE),
consultiamo CF per sapere se c'è stato un riporto finale; nel nostro
caso, CF=0, per cui il risultato è valido così com'è.
Osserviamo che, sommando due numeri positivi, otteniamo un risultato
positivo; di conseguenza, dobbiamo memorizzare il valore 00h nel
BYTE più significativo del risultato.
Se vogliamo visualizzare il risultato in formato Packed BCD, possiamo
utilizzare lo stesso procedimento illustrato per AAA; in questo caso,
l'output inizia da [pbcdSomma+9].
18.6.2 Effetti provocati da DAA sugli operandi e sui flags
L'esecuzione dell'istruzione DAA provoca la modifica del solo registro
AL.
L'esecuzione dell'istruzione DAA provoca la modifica dei campi
CF, PF, AF, ZF, SF e OF del Flags
Register; il solo campo OF assume un valore indeterminato e non ha
quindi alcun significato.
I campi PF, ZF e SF forniscono informazioni ordinarie
relative al numero binario memorizzato in AL da DAA; in sostanza,
PF indica se AL contiene un numero pari o dispari di bit a livello
logico 1, ZF indica se il contenuto di AL vale zero,
SF indica se il bit più significativo di AL vale 1.
Generalmente, anche il contenuto di questi tre flags viene ignorato.
Se la somma Packed BCD provoca un riporto dal nibble basso al nibble
alto, la CPU pone AF=1; in caso contrario si ha AF=0.
Se la somma Packed BCD provoca un riporto al byte successivo, la
CPU pone CF=1; in caso contrario si ha CF=0.
18.7 L'istruzione DAS
Con il mnemonico DAS si indica l'istruzione Decimal Adjust AL after
Subtraction (aggiustamento del contenuto di AL dopo una sottrazione
tra due numeri Packed BCD a 8 bit); lo scopo di questa istruzione
è quello di aggiustare il risultato di una differenza tra due numeri Packed
BCD a 8 bit, in modo da farlo apparire formalmente identico a quello
che si ottiene in base 10.
In sostanza, DAS presuppone che il programmatore abbia appena eseguito
una istruzione SUB (o SBB) tra due numeri Packed BCD a
8 bit; in tal caso, DAS legge il risultato della differenza e
lo converte in formato Packed BCD a 8 bit.
L'unica forma lecita per l'istruzione DAS è la seguente:
DAS
Come si può notare, il programmatore non deve specificare alcun operando
esplicito; infatti, la CPU assume che la differenza si trovi nel
registro AL (operando SRC) e utilizza come operando
DEST, lo stesso registro AL.
L'istruzione DAS deve simulare, in formato Packed BCD, gli
identici risultati che si ottengono sottraendo due numeri interi decimali,
ciascuno compreso tra 0 e 99; di conseguenza, DAS
deve anche simulare un eventuale prestito dal nibble alto al nibble basso
(AF) e un eventuale prestito dal byte successivo (CF).
Si possono presentare allora diverse possibilità, che vengono segnalate
dalla CPU attraverso la modifica (separata) dei flags AF e
CF; a tale proposito, analizziamo alcuni esempi pratici.
Vogliamo calcolare:
37 - 12 = 25
Poniamo AL=37h, BL=12h ed eseguiamo l'istruzione:
sub al, bl
In presenza di questa istruzione, la CPU calcola:
AL = AL - BL = 37h - 12h = 00110111b - 00010010b = 00100101b = 25h, con AF = 0, CF = 0
A questo punto, dobbiamo eseguire l'istruzione:
das
L'istruzione SUB ha prodotto AF=0 (nessun prestito dal nibble
alto al nibble basso) e inoltre, la cifra 5h contenuta nel nibble
basso di AL, non supera 9h; di conseguenza, tale cifra non
necessita di alcun aggiustamento (AF e CF rimangono a
0).
I calcoli precedenti hanno prodotto CF=0 (nessun prestito dal byte
successivo) e inoltre, la cifra 2h contenuta nel nibble alto di
AL, non supera 9h; di conseguenza, tale cifra non necessita
di alcun aggiustamento (CF rimane a 0).
Le elaborazioni eseguite dalla CPU hanno prodotto AF=0 e
CF=0; alla fine otteniamo il risultato Packed BCD dato da
AL=25h.
Vogliamo calcolare:
45 - 18 = 27
Poniamo AL=45h, BL=18h, ed eseguiamo l'istruzione:
sub al, bl
In presenza di questa istruzione, la CPU calcola:
AL = AL - BL = 45h - 18h = 01000101b - 00011000b = 00101101b = 2Dh, con AF = 1, CF = 0
A questo punto, dobbiamo eseguire l'istruzione:
das
L'istruzione SUB ha prodotto AF=1 (prestito dal nibble alto
al nibble basso); di conseguenza, la cifra contenuta nel nibble basso di
AL necessita sicuramente di un aggiustamento.
La CPU sottrae 06h al contenuto di AL e ottiene:
AL = AL - 06h = 2Dh - 06h = 27h, con TempCF = 0
Inoltre, la CPU pone:
AF = 1
e
CF = CF OR TempCF = 0 OR 0 = 0
I calcoli precedenti hanno prodotto CF=0 (nessun prestito dal byte
successivo) e inoltre, la cifra 2h contenuta nel nibble alto di
AL, non supera 9h; di conseguenza, tale cifra non necessita
di alcun aggiustamento (CF rimane a 0).
Le elaborazioni eseguite dalla CPU hanno prodotto AF=1 e
CF=0; alla fine otteniamo il risultato Packed BCD dato da
AL=27h.
Vogliamo calcolare:
38 - 71 = 67 (con prestito di 1)
Poniamo AL=38h, BL=71h, ed eseguiamo l'istruzione:
sub al, bl
In presenza di questa istruzione, la CPU calcola:
AL = AL - BL = 38h - 71h = 00111000b - 01110001b = 11000111b = C7h, con AF = 0, CF = 1
A questo punto, dobbiamo eseguire l'istruzione:
das
L'istruzione SUB ha prodotto AF=0 (nessun prestito dal nibble
alto al nibble basso) e inoltre, la cifra 7h contenuta nel nibble
basso di AL, non supera 9h; di conseguenza, tale cifra non
necessita di alcun aggiustamento (AF rimane a 0 e CF
rimane a 1).
I calcoli precedenti hanno prodotto CF=1 (prestito dal byte successivo);
di conseguenza, la cifra contenuta nel nibble alto di AL necessita
sicuramente di un aggiustamento.
La CPU sottrae 60h al contenuto di AL e ottiene:
AL = AL - 60h = C7h - 60h = 67h
Inoltre, la CPU pone:
CF = 1
Le elaborazioni eseguite dalla CPU hanno prodotto AF=0 e
CF=1; alla fine otteniamo il risultato Packed BCD dato da
AL=67h.
Vogliamo calcolare:
31 - 79 = 52 (con prestito di 1)
Poniamo AL=31h, BL=79h, ed eseguiamo l'istruzione:
sub al, bl
In presenza di questa istruzione, la CPU calcola:
AL = AL - BL = 31h - 79h = 00110001b - 01111001b = 10111000b = B8h, con AF = 1, CF = 1
A questo punto, dobbiamo eseguire l'istruzione:
das
L'istruzione SUB ha prodotto AF=1 (prestito dal nibble alto
al nibble basso); di conseguenza, la cifra contenuta nel nibble basso di
AL necessita sicuramente di un aggiustamento.
La CPU sottrae 06h al contenuto di AL, e ottiene:
AL = AL - 06h = B8h - 06h = B2h, con TempCF = 0
Inoltre, la CPU pone:
AF = 1
e
CF = CF OR TempCF = 1 OR 0 = 1
I calcoli precedenti hanno prodotto CF=1 (prestito dal byte successivo);
di conseguenza, la cifra contenuta nel nibble alto di AL necessita
sicuramente di un aggiustamento.
La CPU sottrae 60h al contenuto di AL, e ottiene:
AL = AL - 60h = B2h - 60h = 52h
Inoltre, la CPU pone:
CF = 1
Le elaborazioni eseguite dalla CPU hanno prodotto AF=1 e
CF=1; alla fine otteniamo il risultato Packed BCD dato da
AL=52h.
Riassumendo, la CPU utilizza AF per indicarci se la differenza
Packed BCD ha prodotto un prestito dal nibble alto al nibble basso;
analogamente, la CPU utilizza CF per indicarci se la differenza
Packed BCD ha prodotto un prestito da un ipotetico gruppo successivo
di cifre Packed BCD. Il risultato della differenza, memorizzato in
AL, è identico a quello che si ottiene in base 10; inoltre,
attraverso CF, possiamo sottrarre numeri Packed BCD di ampiezza
arbitraria.
18.7.1 Differenza tra numeri Packed BCD di ampiezza arbitraria
Sulla base di quanto è stato esposto in precedenza, possiamo intuire che
la differenza tra numeri Packed BCD di ampiezza arbitraria, può
essere svolta in modo ancora più semplice rispetto al caso dei numeri
Unpacked BCD; in relazione all'esempio presentato per l'istruzione
AAS, le uniche differenze sono le seguenti:
- con i numeri Packed BCD operiamo su due cifre per volta
- al posto dell'istruzione AAS dobbiamo utilizzare DAS
Vediamo subito un esempio pratico che ci permette di illustrare l'utilizzo
dei numeri Packed BCD standard; a tale proposito, supponiamo di voler
eseguire la seguente somma tra numeri interi decimali da 18 cifre
ciascuno:
Convertendo tutto in formato Packed BCD standard, dobbiamo ottenere:
Osserviamo subito che il primo numero è negativo (80h), mentre il secondo
è positivo (00h) e inoltre, il primo numero è maggiore del secondo in
valore assoluto; di conseguenza, il procedimento da seguire consiste nel calcolare
una differenza tra numeri positivi, assegnando poi il segno negativo (80h)
al risultato.
Procediamo allora nel seguente modo:
Il codice che esegue la differenza assume il seguente aspetto:
Dopo l'esecuzione dell'ultima differenza (tra gli ottavi BYTE),
consultiamo CF per sapere se c'è stato un prestito finale; nel
nostro caso, CF=0, per cui il risultato è valido così com'è.
Se vogliamo visualizzare il risultato in formato Packed BCD, possiamo
utilizzare lo stesso procedimento illustrato per AAA; in questo caso,
l'output inizia da [pbcdDiff+9].
18.7.2 Effetti provocati da DAS sugli operandi e sui flags
L'esecuzione dell'istruzione DAS provoca la modifica del solo registro
AL.
L'esecuzione dell'istruzione DAS provoca la modifica dei campi
CF, PF, AF, ZF, SF e OF del Flags
Register; il solo campo OF assume un valore indeterminato e non ha
quindi alcun significato.
I campi PF, ZF e SF forniscono informazioni ordinarie
relative al numero binario memorizzato in AL da DAS; in sostanza,
PF indica se AL contiene un numero pari o dispari di bit a livello
logico 1, ZF indica se il contenuto di AL vale zero,
SF indica se il bit più significativo di AL vale 1.
Generalmente, anche il contenuto di questi tre flags viene ignorato.
Se la differenza Packed BCD provoca un prestito dal nibble alto al nibble
basso, la CPU pone AF=1; in caso contrario si ha AF=0.
Se la differenza Packed BCD provoca un prestito dal byte successivo, la
CPU pone CF=1; in caso contrario si ha CF=0.
18.8 Conversione da Packed BCD a Unpacked BCD e viceversa
Le CPU della famiglia 80x86, non dispongono di istruzioni per
l'aggiustamento decimale di moltiplicazioni e divisioni; per i numeri
Packed BCD quindi, non esiste alcuna istruzione equivalente a
AAM e AAD. Eventualmente, il programmatore può scrivere
apposite procedure che svolgono questo tipo di operazioni; a tale proposito,
è necessario servirsi degli stessi concetti su cui si basa il principio di
funzionamento delle istruzioni AAM e AAD.
In alternativa, si può anche seguire un'altra strada che consiste nel
convertire i numeri BCD, da un formato all'altro; volendo eseguire,
ad esempio, una moltiplicazione tra numeri Packed BCD di ampiezza
arbitraria, possiamo pensare di convertire i due fattori, da Packed
BCD a Unpacked BCD, svolgere i calcoli con MUL e
AAM e poi convertire il risultato da Unpacked BCD a
Packed BCD.
18.8.1 Conversione da Packed BCD a Unpacked BCD
La conversione da Packed BCD a Unpacked BCD si svolge in
modo semplicissimo grazie all'istruzione AAM; infatti, sappiamo
che AAM legge da AL il risultato di una moltiplicazione
tra fattori Unpacked BCD, lo converte in formato Unpacked
BCD e lo memorizza in AH:AL.
Consideriamo, ad esempio, il seguente prodotto:
AL = 03h * 09h = 1Bh = 27
Una successiva istruzione AAM calcola, come sappiamo:
1Bh / Ah, AH = Q = 02h, AL = R = 07h
L'istruzione AAM utilizza la base predefinita 10 e proprio
per questo motivo, ottiene un risultato decimale; il trucco da utilizzare,
consiste allora nel servirsi di AAM con base 16!
Come già sappiamo, per imporre a AAM l'utilizzo della base
16, dobbiamo servirci del codice macchina D4h, 16; nel
caso dell'esempio precedente, otteniamo allora:
1Bh / 10h, AH = Q = 01h, AL = R = 0Bh
Come si può notare, abbiamo convertito 1Bh in 01h:0Bh!
Una volta chiarito questo trucco, possiamo passare ad un esempio pratico;
a tale proposito, supponiamo di voler convertire in formato Unpacked
BCD, il numero Packed BCD standard 80743251687690345672h.
Come sappiamo, la versione Unpacked BCD di un numero, occupa il
doppio dei byte rispetto alla versione Packed BCD; di conseguenza,
possiamo procedere con le seguenti definizioni:
Il seguente codice esegue la conversione da Packed BCD standard a
Unpacked BCD:
Per ogni conversione, il puntatore (SI) a pbcdNumber deve
essere incrementato di 1 byte, mentre il puntatore (DI) a
ubcdNumber deve essere incrementato di 2 byte; infatti,
ogni BYTE di pbcdNumber (che contiene una coppia di cifre
in formato Packed BCD), deve essere convertito in una WORD
di ubcdNumber (che contiene una coppia di cifre in formato
Unpacked BCD).
Osserviamo che scompattando anche il segno 80h di pbcdNumber,
otteniamo 0800h; il programmatore deve quindi tenere presente che
la WORD più significativa di ubcdNumber, contiene la
codifica del segno del numero appena convertito.
Se vogliamo visualizzare il risultato in formato Unpacked BCD,
possiamo utilizzare lo stesso procedimento illustrato per AAA;
in questo caso, l'output inizia da [ubcdNumber+19].
18.8.2 Conversione da Unpacked BCD a Packed BCD
La conversione da Unpacked BCD a Packed BCD si svolge in
modo semplicissimo grazie all'istruzione AAD; infatti, sappiamo
che AAD prepara il contenuto Unpacked BCD della coppia
AH:AL, in modo che una successiva istruzione DIV fornisca
un quoziente e un resto, entrambi in formato Unpacked
BCD.
Supponiamo, ad esempio, di avere AH:AL=01h:08h; una successiva
istruzione AAD calcola, come sappiamo:
(01h * Ah) + 08h = 0Ah + 08h = 12h = 18
L'istruzione AAD utilizza la base predefinita 10 e proprio
per questo motivo, ottiene un risultato decimale; il trucco da utilizzare,
consiste allora nel servirsi di AAD con base 16!
Come già sappiamo, per imporre a AAD l'utilizzo della base
16, dobbiamo servirci del codice macchina D5h, 16; nel
caso dell'esempio precedente, otteniamo allora:
(01h * 10h) + 08h = 10h + 08h = 18h
Come si può notare, abbiamo convertito 01h:08h in 18h!
Una volta chiarito questo trucco, possiamo passare ad un esempio pratico;
a tale proposito, supponiamo di voler convertire in formato Packed
BCD standard, il numero Unpacked BCD dell'esempio precedente,
rappresentato da 0800070403020501060807060900030405060702h.
Come sappiamo, la versione Packed BCD standard di un numero, occupa
la metà dei byte rispetto alla versione Unpacked BCD; di conseguenza,
possiamo procedere con le seguenti definizioni:
Il seguente codice esegue la conversione da Unpacked BCD a Packed
BCD standard:
Per ogni conversione, il puntatore (SI) a ubcdNumber deve
essere incrementato di 2 byte, mentre il puntatore (DI) a
pbcdNumber deve essere incrementato di 1 byte; infatti,
ogni WORD di ubcdNumber (che contiene una coppia di cifre
in formato Unpacked BCD), deve essere convertita in un BYTE
di pbcdNumber (che contiene una coppia di cifre in formato
Packed BCD).
Se vogliamo visualizzare il risultato in formato Packed BCD,
possiamo utilizzare lo stesso procedimento illustrato per AAA;
in questo caso, l'output inizia da [pbcdNumber+9].
18.9 Conversione da BCD a binario e viceversa
Quando le elaborazioni da svolgere sui numeri BCD diventano troppo
complesse, potrebbe risultare conveniente una conversione in binario dei
numeri stessi; in questo modo, si effettuano le varie elaborazioni sui
numeri binari e si provvede poi a riconvertire in formato BCD i
risultati appena ottenuti.
Per effettuare le conversioni da BCD a binario e viceversa, possiamo
ricorrere, ad esempio, al metodo delle divisioni successive; come
è stato già ricordato anche in questo capitolo, dividendo ripetutamente
un numero in base b1, per una base b2, si ottengono una serie
di resti che formano la sequenza delle cifre, in base b2, del numero
stesso. Le divisioni terminano non appena si ottiene un quoziente
nullo; l'ultimo resto ottenuto rappresenta la cifra più significativa del
numero appena convertito in base b2.
Il problema fondamentale consiste nel fatto che dobbiamo cercare di ricondurci
sempre al caso di una divisione tra un dividendo di ampiezza arbitraria
e un divisore formato da una sola cifra (che rappresenta la base di
destinazione); in questo modo, possiamo applicare gli stessi procedimenti già
illustrati in questo capitolo e nel capitolo precedente.
18.9.1 Conversione da BCD a binario
Per la conversione da BCD a binario, dobbiamo utilizzare la cifra
2 (base dei numeri binari) come divisore; in questo modo,
è possibile applicare lo stesso procedimento già illustrato in questo
capitolo per l'istruzione AAD (ovviamente, se il dividendo
è in formato Packed BCD, dobbiamo prima convertirlo in formato
Unpacked BCD).
Supponiamo, ad esempio, di voler convertire in binario il numero decimale
07050301h (in formato Unpacked BCD); dividendo ripetutamente
tale numero per 02h con l'ausilio di AAD e DIV,
otteniamo:
07050301h / 02h, Q = 03070605h, R = 01h
03070605h / 02h, Q = 01080802h, R = 01h
01080802h / 02h, Q = 00090401h, R = 00h
00090401h / 02h, Q = 00040700h, R = 01h
00040700h / 02h, Q = 00020305h, R = 00h
00020305h / 02h, Q = 00010107h, R = 01h
00010107h / 02h, Q = 00000508h, R = 01h
00000508h / 02h, Q = 00000209h, R = 00h
00000209h / 02h, Q = 00000104h, R = 01h
00000104h / 02h, Q = 00000007h, R = 00h
00000007h / 02h, Q = 00000003h, R = 01h
00000003h / 02h, Q = 00000001h, R = 01h
00000001h / 02h, Q = 00000000h, R = 01h
Unendo assieme i vari resti (a partire dall'ultimo), otteniamo il numero
binario a 13 cifre 1110101101011b; come verifica, osserviamo
che traducendo tale numero in base 10, risulta:
Per poter memorizzare i vari resti nei singoli bit di un numero binario,
abbiamo bisogno delle istruzioni logiche; tali istruzioni, vengono illustrate
nel capitolo successivo.
18.9.2 Conversione da binario a BCD
Per la conversione da binario a BCD, dobbiamo utilizzare la cifra
Ah (base dei numeri decimali) come divisore; in questo modo,
è possibile applicare lo stesso procedimento già illustrato nel capitolo
precedente per l'istruzione DIV.
Supponiamo, ad esempio, di voler convertire in BCD il numero binario
1110101101011b=1D6Bh che abbiamo ottenuto in precedenza; dividendo
ripetutamente tale numero per Ah con l'ausilio di DIV,
otteniamo:
1DB6h / Ah, Q = 02F1h, R = 1h
02F1h / Ah, Q = 004Bh, R = 3h
004Bh / Ah, Q = 0007h, R = 5h
0007h / Ah, Q = 0000h, R = 7h
Unendo assieme i vari resti (a partire dall'ultimo), otteniamo il numero
decimale a 4 cifre 7531; avendo a disposizione le singole
cifre del numero, possiamo facilmente memorizzarle in formato Unpacked
BCD (per il formato Packed BCD abbiamo bisogno delle istruzioni
logiche).