Assembly Avanzato con MASM
Capitolo 18: Il set di istruzioni della FPU
18.1 Struttura di una istruzione numerica
L'opcode di una istruzione numerica è composto da almeno due byte, i quali possono essere
seguiti da altri campi come il SIB e il Displacement; per maggiori dettagli
su questa terminologia, si consiglia una rilettura del Capitolo 11, tutorial Assembly
Base. La Figura 18.1 illustra le cinque possibili configurazioni dell'opcode di una
istruzione numerica.
Come già sappiamo, i cinque bit più significativi contengono la sequenza di escape
11011b; la restante parte dell'opcode è codificata nei bit indicati con
op in Figura 18.1.
Le prime due configurazioni contengono anche i campi mod e r/m, il cui
significato è identico a quello già illustrato nel Capitolo 11 (tutorial Assembly
Base) per le istruzioni della CPU; solo tali due configurazioni possono
essere seguite dai campi opzionali SIB e Displacement. Per la seconda
configurazione, il campo MF indica il Memory Format di un eventuale
operando rappresentato da una locazione di memoria; il significato di questi due bit
è il seguente:
- 00b = 32 bit real (Single Real)
- 01b = 32 bit integer (Short Integer)
- 10b = 64 bit real (Double Real)
- 11b = 16 bit integer (Word Integer)
Come viene illustrato più avanti, ulteriori formati vengono gestiti implicitamente o
esplicitamente con le altre configurazioni dell'opcode.
Nella terza configurazione, se l'istruzione indica esplicitamente la presenza di due
operandi rappresentati da due registri dello stack (uno dei quali deve essere
obbligatoriamente ST(0)), il bit di direzione d indica se la destinazione
è ST(0) (d=0) o ST(i) (d=1). L'indice i è
contenuto nei tre bit del campo ST(i) in Figura 18.1 (con tre bit possiamo
specificare un indice compreso tra 0 e 7). Il bit d si combina
inoltre con i due bit del campo R per stabilire qual è l'operando di riferimento.
Il bit P (pop) indica se la FPU deve eseguire (P=1) o no
(P=0) una estrazione dallo stack al termine di una operazione.
Dalle considerazioni appena esposte si vede che esistono istruzioni numeriche prive di
operandi, altre con uno o due operandi impliciti e altre ancora con uno o due operandi
espliciti. Si tenga anche presente che le istruzioni numeriche possono accedere
direttamente solo ai registri dello stack della FPU e alle locazioni di memoria
(via CPU); non è consentito invece l'accesso diretto ai registri della CPU,
ad eccezione dell'accumulatore AX per le istruzioni FSTSW e FNSTSW.
Vediamo un esempio relativo all'istruzione FADD, la quale permette di sommare un
numero reale al contenuto di ST(i); il risultato viene salvato nello stesso
ST(i). L'operando sorgente può essere una locazione di memoria contenente
un numero reale da 32 o 64 bit, oppure un registro dello stack contenente
quindi un numero reale da 80 bit; l'operando destinazione deve essere
obbligatoriamente un registro dello stack. Se l'operando destinazione non viene indicato
esplicitamente, esso è implicitamente ST(0); inoltre, è consentito sommare
ST(0) a se stesso.
I quattro bit nelle posizioni da 12 a 15 dell'opcode valgono sempre
Dh (1101b), mentre il bit in posizione 11 vale sempre 1 in
modo da formare la sequenza di escape 11011b. Se indichiamo in modo esplicito il
solo operando sorgente, rappresentato da una locazione di memoria, viene usata la seconda
configurazione di Figura 18.1; in tal caso, abbiamo visto che il campo MF vale
00b per il Single Real e 10b per il Double Real. Tutti gli
altri bit vengono posti a zero, per cui, nel caso di un operando sorgente di tipo
mem a 32 bit si ha:
11011000b 00000000b = D8h 00h
Nel caso di un operando sorgente di tipo mem a 64 bit si ha:
11011100b 00000000b = DCh 00h
In base al contenuto dei campi mod e r/m, questi opcode sono seguiti
eventualmente dai campi SIB e Displacement, che indicano il metodo di
indirizzamento usato per accedere all'operando di tipo mem (Capitolo 11 del
tutorial Assembly Base); ovviamente, tale operando rappresenta sempre la
sorgente, mentre la destinazione implicita è ST(0).
Se si indicano esplicitamente i due operandi ST(0) e ST(i), viene usata
la terza configurazione di Figura 18.1; in tal caso, come abbiamo visto, d=0
indica che ST(0) è la destinazione, mentre d=1 indica che ST(0) è
la sorgente.
P=0 in quanto non è richiesta alcuna estrazione dallo stack; l'indice i
di ST(i) viene indicato attraverso i tre bit nelle posizioni da 0 a
2. Tutti gli altri bit vengono posti a zero, per cui, nel caso di ST(0)
destinazione si ha:
11011000b (11000000b + i) = D8h (C0h + i)
Nel caso di ST(0) sorgente si ha:
11011100b (11000000b + i) = DCh (C0h + i)
18.2 Set di istruzioni della FPU
Nel seguito del capitolo si fa ricorso ad una serie di simboli e abbreviazioni; per
quanto riguarda gli opcode, si usano i seguenti simboli:
- /c (dove c è una cifra compresa tra 0 e 7) indica
che nel secondo byte dell'opcode viene usato solo il campo r/m per
contenere la cifra c che rappresenta una continuazione dell'opcode
stesso
- i (dove i è una cifra compresa tra 0 e 7) indica
l'indice di un registro ST(i) della FPU, che viene sommato
al valore esadecimale alla sua sinistra per ottenere l'opcode
Per le istruzioni vengono usate le seguenti abbreviazioni:
- m32fp: operando di tipo Single Real a 32 bit in memoria
- m64fp: operando di tipo Double Real a 64 bit in memoria
- m80fp: operando di tipo Extended Real a 80 bit in memoria
- m16int: operando di tipo Word Integer a 16 bit in memoria
- m32int: operando di tipo Short Integer a 32 bit in memoria
- m64int: operando di tipo Long Integer a 64 bit in memoria
- m80dec: operando di tipo Packed BCD a 80 bit in memoria
- ST(0): registro in cima allo stack della FPU
- ST(i): registro in posizione i nello stack della FPU
- mem16: generica locazione di memoria da 16 bit
- mem14byte: generica locazione di memoria da 14 byte
- mem28byte: generica locazione di memoria da 28 byte
- mem94byte: generica locazione di memoria da 94 byte
- mem108byte: generica locazione di memoria da 108 byte
- SRC: operando sorgente
- DEST: operando destinazione
Le istruzioni che la FPU mette a disposizione possono essere raggruppate nelle
seguenti sei categorie:
- istruzioni per il trasferimento dati
- istruzioni per l'aritmetica di base
- istruzioni di comparazione
- istruzioni per le funzioni trascendenti
- istruzioni per le costanti
- istruzioni per il controllo della FPU
Alcune istruzioni comportano l'inserimento di un nuovo dato nello stack (PUSH);
ricordando che lo stack si riempie dall'alto verso il basso (da R7 verso R0),
tale inserimento viene eseguito nel modo seguente:
- il puntatore alla cima dello stack (TOP) viene decrementato di 1
- il dato viene inserito nel nuovo ST(0)
Alcune istruzioni comportano l'estrazione di un dato dallo stack (POP); ricordando
che lo stack si riempie dall'alto verso il basso (da R7 verso R0), tale
estrazione viene eseguita nel modo seguente:
- il dato viene estratto dallo stack
- il contenuto dell'attuale ST(0) viene etichettato come "vuoto"
- il puntatore alla cima dello stack (TOP) viene incrementato di 1
Nel seguito vengono analizzate in dettaglio le istruzioni della FPU; per ciascuna
di esse viene indicato il compito svolto, il codice macchina, il tipo di operandi, lo
stato dei flags C0, C1, C2, C3 e le possibili eccezioni della
FPU. Si può notare che tutti i mnemonici iniziano per F.
18.2.1 L'istruzione F2XM1 - Compute 2x - 1
Questa istruzione legge il contenuto \(x\) di ST(0), calcola \(2^x - 1\) e salva
il risultato nello stesso ST(0).
Non è richiesto alcun operando esplicito; ST(0) viene usato come operando implicito.
Affinché F2XM1 produca un risultato valido, si deve avere \(-0.1 \le x \le +0.1\);
in caso contrario, la FPU genera un risultato indefinito. Se \(x\) è un valore
valido, si ottengono i seguenti risultati:
\begin{align}
-1.0 \le x \le -0 &\implies -0.5 \le 2^x - 1 \le -0 \cr
x = -0 &\implies 2^x - 1 = -0 \cr
x = +0 &\implies 2^x - 1 = +0 \cr
+0 \le x \le +1.0 &\implies +0 \le 2^x - 1 \le +1.0
\end{align}
Se si ha la necessità di usare una base \(m \ne 2\), per giungere a \(m^x\) si può
sfruttare il fatto che la FPU dispone di una istruzione per il calcolo di
\(a \cdot \log_2 b\) (vedere più avanti l'istruzione FYL2X); si tratta allora
di applicare la seguente semplice proprietà matematica:
\[ \log_2 {m^x} = x \cdot \log_2 m \implies m^x = 2^{x \cdot \log_2 m} \]
18.2.2 L'istruzione FABS - Absolute Value
Questa istruzione produce il valore assoluto del numero reale \(x\) contenuto in
ST(0); a tale proposito, viene posto a zero il suo bit di segno.
Non è richiesto alcun operando esplicito; ST(0) viene usato come operando implicito.
A seconda del contenuto \(x\) di ST(0), l'istruzione FABS produce i seguenti
risultati (F indica un numero in floating point):
\begin{align}
x = -\infty &\implies |x| = +\infty \cr
x = -F &\implies |x| = +F \cr
x = -0 &\implies |x| = +0 \cr
x = +0 &\implies |x| = +0 \cr
x = +F &\implies |x| = +F \cr
x = +\infty &\implies |x| = +\infty \cr
x = NaN &\implies |x| = NaN
\end{align}
18.2.3 Le istruzioni FADD, FADDP e FIADD - Add
Queste istruzioni sommano l'operando sorgente con l'operando destinazione e salvano
il risultato nell'operando destinazione; si ha quindi:
\[ {DEST} = {SRC} + {DEST} \]
L'operando destinazione deve essere obbligatoriamente un registro dello stack. Se
l'operando sorgente si trova in memoria, esso può essere un Single Real, un
Double Real, un Word Integer o uno Short Integer; se si intende
eseguire la somma tra due Extended Real, bisogna usare due registri dello stack.
In assenza di operandi espliciti, vengono usati implicitamente ST(0) come
sorgente e ST(1) come destinazione. In presenza di un solo operando esplicito
(che ricopre il ruolo di sorgente), viene usato implicitamente ST(0) come
destinazione. In alternativa, si può usare esplicitamente ST(i) come sorgente
e ST(0) come destinazione o viceversa; è anche possibile quindi sommare
ST(0) a se stesso scrivendo (sintassi MASM):
fadd ST(0), ST(0)
L'istruzione FADD esegue la somma e salva il risultato nell'operando destinazione
dello stack, sovrascrivendo il vecchio contenuto.
L'istruzione FADDP esegue la somma, salva il risultato nell'operando destinazione
e effettua un POP; nella versione di FADDP priva di operandi espliciti,
tale POP sposta il risultato in ST(0).
L'istruzione FIADD richiede un intero come operando sorgente; tale operando viene
convertito in Extended Real prima di essere sommato all'operando destinazione.
La tabella seguente illustra il risultato che si ottiene sommando tra loro vari tipi di
numeri nel caso in cui non si verifichino overflow o underflow; F indica un numero
in floating point, I un numero intero, SRC l'operando sorgente e DEST
l'operando destinazione.
(*) Eccezione di operazione aritmetica non valida. Forme indeterminate
\(({+\infty}) + ({-\infty})\) o \(({-\infty}) + ({+\infty})\).
18.2.4 L'istruzione FBLD - Load Binary Coded Decimal
Questa istruzione legge il contenuto di una locazione di memoria da 80 bit,
assumendo che si tratti di un Packed BCD, lo converte in formato Extended
Real e lo carica (PUSH) nella nuova cima dello stack; come è stato spiegato
nel precedente capitolo, un numero intero codificato in Packed BCD standard
viene convertito in formato Extended Real senza errori di arrotondamento.
Un m80dec deve essere specificato in modo esplicito come operando sorgente;
ST(0) è implicitamente l'operando destinazione.
FBLD si aspetta che ogni cifra del numero BCD sia compresa tra 0h
e 9h e non effettua alcuna verifica su tale requisito; se si prova a caricare
in ST(0) un numero BCD non valido (ad esempio, con cifre da Ah a
Fh), si ottiene un risultato indefinito.
18.2.5 L'istruzione FBSTP - Store BCD Integer and Pop
Questa istruzione legge il contenuto di ST(0), lo converte in formato Packed
BCD e lo salva in una locazione di memoria da 80 bit; infine, effettua un
POP.
ST(0) è la sorgente implicita mentre un m80dec deve essere indicato
esplicitamente come destinazione.
A seconda del contenuto di ST(0), nell'operando destinazione verranno salvati
i seguenti valori, eventualmente arrotondati ad un intero Packed BCD in base al
metodo di arrotondamento in uso:
\begin{align}
-\infty &\implies * \cr
F \le -1 &\implies -BCD \cr
-1 \lt F \lt -0 &\implies ** \cr
-0 &\implies -0 \cr
+0 &\implies +0 \cr
+0 \lt F \lt +1 &\implies ** \cr
F \ge +1 &\implies +BCD \cr
+\infty &\implies * \cr
NaN &\implies *
\end{align}
F = numero in floating point.
BCD = numero Packed BCD.
\(-\infty\) comprende anche il caso di un numero negativo troppo grande in valore assoluto.
\(+\infty\) comprende anche il caso di un numero positivo troppo grande.
(*) operazione aritmetica non valida (eccezione IE).
(**) \({\pm 0}\) o \({\pm 1}\) a seconda del modo di arrotondamento in uso.
In caso di operazione aritmetica non valida, se IE non è mascherato, viene
generata un'eccezione e nessun valore viene salvato in DEST; se IE è
mascherato, viene salvato in DEST il valore BCD Indefinite.
18.2.6 L'istruzione FCHS - Change Sign
Questa istruzione inverte (complementa) il segno del numero in floating point contenuto
in ST(0); un numero positivo diventa negativo e viceversa.
Non è richiesto alcun operando esplicito; ST(0) viene usato come operando implicito.
A seconda del contenuto \(x\) di ST(0), si ottengono i seguenti risultati (F
è un numero in floating point):
\begin{align}
x = -\infty &\implies -x = +\infty \cr
x = -F &\implies -x = +F \cr
x = -0 &\implies -x = +0 \cr
x = +0 &\implies -x = -0 \cr
x = +F &\implies -x = -F \cr
x = +\infty &\implies -x = -\infty \cr
x = NaN &\implies -x = NaN
\end{align}
18.2.7 Le istruzioni FCLEX e FNCLEX - Clear Exceptions
Queste istruzioni riportano a zero i flags PE, UE, OE, ZE,
DE, IE, l'exception summary status flag ES, lo stack fault flag
SF e il busy flag B nel registro Status Word della FPU.
Non è richiesto alcun operando esplicito.
A differenza di FNCLEX, l'istruzione FCLEX gestisce anche eventuali
eccezioni non mascherate in attesa, prima di azzerare i vari flags.
In presenza di una istruzione FCLEX, la FPU esegue in sequenza le due
istruzioni FWAIT (vedere più avanti) e FNCLEX.
18.2.8 Le istruzioni FCMOVcc - Floating-Point Conditional Move
Queste istruzioni effettuano un test sui flags di stato nel registro EFLAGS
della CPU; se il test produce un esito positivo, il contenuto dell'operando
sorgente viene copiato nell'operando destinazione.
L'operando sorgente è ST(i) e deve essere specificato in modo esplicito;
l'operando destinazione è ST(0) e deve essere specificato in modo esplicito.
Nel seguito, CF indica il Carry Flag, ZF indica lo Zero Flag
e PF indica il Parity Flag; la stringa cc nel mnemonico indica il
condition code che stabilisce se il trasferimento dati debba essere effettuato o
meno.
I due operandi SRC e DEST vengono considerati "unordered" quando
tra essi non è possibile stabilire una relazione d'ordine (maggiore, minore o uguale);
a tale proposito, si veda più avanti l'istruzione FXAM, la quale permette di
stabilire la classe a cui appartiene un determinato operando.
- FCMOVB ST(0), ST(i) - Move if ST(i) below ST(0) (CF=1)
- FCMOVE ST(0), ST(i) - Move if ST(i) equal ST(0) (ZF=1)
- FCMOVBE ST(0), ST(i) - Move if ST(i) below or equal ST(0)
(CF=1 OR ZF=1)
- FCMOVU ST(0), ST(i) - Move if ST(i), ST(0) unordered (PF=1)
- FCMOVNB ST(0), ST(i) - Move if ST(i) not below ST(0) (CF=0)
- FCMOVNE ST(0), ST(i) - Move if ST(i) not equal ST(0) (ZF=0)
- FCMOVNBE ST(0), ST(i) - Move if ST(i) not below or equal ST(0)
(CF=0 AND ZF=0)
- FCMOVNU ST(0), ST(i) - Move if ST(i), ST(0) not unordered
(PF=0)
Non tutte le FPU supportano le istruzioni FCMOVcc; i programmi possono
effettuare le necessarie verifiche attraverso l'istruzione CPUID (vedere la
documentazione in bibliografia).
18.2.9 Le istruzioni FCOM, FCOMP e FCOMPP - Compare Floating-Point Values
Queste istruzioni comparano l'operando sorgente con il contenuto di ST(0); il
risultato della comparazione viene codificato nei flags C0, C2 e
C3 del Condition Code, presenti nel registro Status Word della
FPU.
L'operando destinazione è implicitamente ST(0), mentre l'operando sorgente, se
presente, può essere ST(i) o una locazione di memoria di tipo m32fp o
m64fp; se l'operando sorgente non è presente, esso è implicitamente ST(1).
Come indicano i mnemonici, si tratta chiaramente di una comparazione tra numeri in
floating point; per tali tipi di numeri, si assume \(-{0.0} = +{0.0}\).
La tabella seguente indica i valori assegnati a C0, C2 e C3 in base
al risultato della comparazione. Se i due operandi sono "unordered" e il flag
IE non è mascherato, viene generata un'eccezione (operazione aritmetica non
valida) e i flags del Condition Code risultano indeterminati; ciò vale anche
quando almeno uno degli operandi è un NaN o un formato non supportato.
Queste istruzioni eseguono la comparazione e, se almeno uno degli operandi è un
NaN o un formato non supportato, pongono a 1 il bit IE nel
registro Status Word della FPU; se IE non è mascherato, i
flags del Condition Code non vengono modificati.
L'istruzione FCOMP effettua un POP dopo aver comparato i due operandi.
L'istruzione FCOMPP effettua due POP dopo aver comparato i due operandi.
18.2.10 Le istruzioni FCOMI, FCOMIP, FUCOMI e FUCOMIP -
Compare Floating-Point Values and Set EFLAGS
Queste istruzioni comparano il contenuto di ST(i) con il contenuto di ST(0);
il risultato della comparazione viene codificato nei flags ZF, PF, CF
del registro EFLAGS della CPU.
L'operando sorgente è ST(i) e deve essere specificato in modo esplicito; l'operando
destinazione è ST(0) e deve essere specificato in modo esplicito.
In questo tipo di comparazione, lo zero viene trattato come un numero senza segno, per cui
\(-0.0 = +0.0\).
La tabella seguente indica i valori assegnati a ZF, PF e CF in base
al risultato della comparazione. Se i due operandi sono "unordered" e il flag
IE non è mascherato, viene generata un'eccezione (operazione aritmetica non valida)
e i flags di EFLAGS risultano indeterminati; ciò vale anche quando almeno uno degli
operandi è un NaN o un formato non supportato.
Le istruzioni FCOMI/FCOMIP eseguono la comparazione e, se almeno uno degli operandi
è un NaN o un formato non supportato, pongono a 1 il bit IE nel
registro Status Word della FPU; l'istruzione FCOMIP effettua anche un
POP dopo aver comparato i due operandi.
Le istruzioni FUCOMI/FUCOMIP eseguono la comparazione e, se almeno uno degli operandi
è un sNaN o un formato non supportato, pongono a 1 il bit IE nel
registro Status Word della FPU; l'istruzione FUCOMIP effettua anche un
POP dopo aver comparato i due operandi.
In caso di operazione aritmetica non valida, queste quattro istruzioni pongono a zero i
flags OF, SF e AF di EFLAGS.
Le istruzioni FCOMI, FCOMIP, FUCOMI e FUCOMIP sono state
introdotte a partire dalle CPU di classe P6.
18.2.11 L'istruzione FCOS - Cosine
Questa istruzione legge il contenuto \(x\) di ST(0), calcola \(\cos(x)\) e salva
il risultato nello stesso ST(0).
Non è richiesto alcun operando esplicito. L'operando ST(0) viene usato
implicitamente come sorgente e come destinazione.
Come sappiamo dalla matematica, la funzione \(\cos(x)\) è periodica con periodo \(2\pi\),
è definita in tutto \(\Bbb R\) e \(\forall \ x \in \Bbb R\) si ha \(-1 \le \cos(x) \le +1\);
la Figura 18.2 illustra tutti questi aspetti.
Affinché FCOS produca un risultato valido, \(x\) deve essere espresso in radianti
e si deve avere \(-2^{63} \lt x \lt +2^{63}\); in caso contrario, la FPU non
esegue il calcolo di \(\cos(x)\) e il contenuto di ST(0) resta quindi invariato.
Se \(x\) è un valore valido, si ottengono i seguenti risultati (F indica un numero
in floating point):
\begin{align}
x = -\infty &\implies \cos(x) = * \cr
x = -F &\implies -1 \le \cos(x) \le +1 \cr
x = -0 &\implies \cos(x) = +1 \cr
x = +0 &\implies \cos(x) = +1 \cr
x = +F &\implies -1 \le \cos(x) \le +1 \cr
x = +\infty &\implies \cos(x) = * \cr
x = NaN &\implies \cos(x) = NaN
\end{align}
(*) operazione aritmetica non valida (eccezione IE).
Nel caso in cui \(x\), pur essendo un Extended Real valido, non rientri però nei
limiti esposti in precedenza, FCOS non genera alcuna eccezione di operazione
aritmetica non valida, ma si limita a porre C2=1; è compito dei programmi quindi
testare lo stato di questo flag del Condition Code.
Per evitare grossolani errori di approssimazione, si consiglia di usare FCOS
con \(-(3\pi)/8 \lt x \lt +(3\pi)/8\).
18.2.12 L'istruzione FDECSTP - Decrement Stack-Top Pointer
Questa istruzione decrementa di 1 il puntatore alla cima dello stack (TOP);
l'effetto che si ottiene sui Data Registers della FPU viene illustrato
in modo simbolico dalla Figura 18.3, dove i registri da R0 a R7 si trovano
in una struttura circolare fissa, mentre i puntatori da ST(0) a ST(7)
possono ruotare in senso orario (POP) o in senso antiorario (PUSH).
In questo esempio, si ha TOP=2; la cima dello stack, rappresentata da ST(0),
si trova quindi nel registro R2. L'istruzione FDECSTP ruota sempre i
puntatori in senso antiorario (PUSH) di una posizione; di conseguenza, si ottiene
TOP=1, per cui ST(0) punterà a R1, ST(1) a R2 e così via.
Chiaramente, se si esegue FDECSTP quando TOP=0, si ottiene TOP=7; si
tratta, come sappiamo, del classico wrap around.
L'esecuzione dell'istruzione FDECSTP non modifica il contenuto dei registri dello
stack e della Tag Word; di conseguenza, anche in caso di wrap around, la
FPU non genera alcuna eccezione.
18.2.13 Le istruzioni FDIV, FDIVP e FIDIV - Divide
Queste istruzioni dividono l'operando destinazione (dividendo) con l'operando sorgente
(divisore) e salvano il risultato nell'operando destinazione; si ha quindi:
\[ {DEST} = {{DEST} \over {SRC}} \]
L'operando destinazione deve essere obbligatoriamente un registro dello stack. Se
l'operando sorgente si trova in memoria, esso può essere un Single Real, un
Double Real, un Word Integer o uno Short Integer; se si intende
eseguire la divisione tra due Extended Real, bisogna usare due registri dello
stack.
In assenza di operandi espliciti, vengono usati implicitamente ST(0) come
sorgente e ST(1) come destinazione. In presenza di un solo operando esplicito
(che ricopre il ruolo di sorgente), viene usato implicitamente ST(0) come
destinazione. In alternativa, si può usare esplicitamente ST(i) come sorgente
e ST(0) come destinazione o viceversa; è anche possibile quindi dividere
ST(0) per se stesso scrivendo (sintassi MASM):
fdiv ST(0), ST(0)
L'istruzione FDIV esegue la divisione e salva il risultato nell'operando destinazione
dello stack, sovrascrivendo il vecchio contenuto.
FDIVP esegue la divisione, salva il risultato nell'operando destinazione e effettua
un POP.
FIDIV richiede un intero come operando sorgente; tale operando viene convertito in
Extended Real prima di essere usato come divisore dell'operando destinazione.
La tabella seguente illustra il risultato che si ottiene dividendo tra loro vari tipi di
numeri nel caso in cui non si verifichino overflow o underflow; F indica un numero
in floating point, I un numero intero, SRC l'operando sorgente e DEST
l'operando destinazione.
(*) Eccezione di operazione aritmetica non valida (IE). Forme indeterminate
\(({\pm\infty}) / ({\pm\infty})\) e \(({\pm 0}) / ({\pm 0})\).
(**) Eccezione di divisione per zero (ZE).
Se l'eccezione ZE non è mascherata, la FPU chiama l'apposito Exception
Handler e non salva il risultato nell'operando destinazione; se l'eccezione ZE
è mascherata, la FPU produce come risultato un \(\infty\) con il segno appropriato.
18.2.14 Le istruzioni FDIVR, FDIVRP e FIDIVR - Reverse Divide
Queste istruzioni sono del tutto simili a quelle precedenti, con la differenza che
dividono l'operando sorgente (dividendo) con l'operando destinazione (divisore) e
salvano il risultato nell'operando destinazione; si ha quindi:
\[ {DEST} = {{SRC} \over {DEST}} \]
L'operando destinazione deve essere obbligatoriamente un registro dello stack. Se
l'operando sorgente si trova in memoria, esso può essere un Single Real, un
Double Real, un Word Integer o uno Short Integer; se si intende
eseguire la divisione tra due Extended Real, bisogna usare due registri dello
stack.
In assenza di operandi espliciti, vengono usati implicitamente ST(0) come
sorgente e ST(1) come destinazione. In presenza di un solo operando esplicito
(che ricopre il ruolo di sorgente), viene usato implicitamente ST(0) come
destinazione. In alternativa, si può usare esplicitamente ST(i) come sorgente
e ST(0) come destinazione o viceversa; è anche possibile quindi dividere
ST(0) per se stesso scrivendo (sintassi MASM):
fdivr ST(0), ST(0)
L'istruzione FDIVR esegue la divisione e salva il risultato nell'operando
destinazione dello stack, sovrascrivendo il vecchio contenuto.
FDIVRP esegue la divisione, salva il risultato nell'operando destinazione e
effettua un POP.
FIDIVR richiede un intero come operando sorgente; tale operando viene convertito
in Extended Real prima di essere usato come dividendo con l'operando destinazione.
La tabella seguente illustra il risultato che si ottiene dividendo tra loro vari tipi di
numeri nel caso in cui non si verifichino overflow o underflow; F indica un numero
in floating point, I un numero intero, SRC l'operando sorgente e DEST
l'operando destinazione.
(*) Eccezione di operazione aritmetica non valida (IE). Forme indeterminate
\(({\pm\infty}) / ({\pm\infty})\) e \(({\pm 0}) / ({\pm 0})\).
(**) Eccezione di divisione per zero (ZE).
Se l'eccezione ZE non è mascherata, la FPU chiama l'apposito Exception
Handler e non salva il risultato nell'operando destinazione; se l'eccezione ZE
è mascherata, la FPU produce come risultato un \(\infty\) con il segno appropriato.
18.2.15 L'istruzione FFREE - Free Floating-Point Register
Questa istruzione richiede esplicitamente come operando un registro ST(i) della
FPU; il relativo campo di indice i nella Tag Word viene posto a
11b (vuoto), mentre il contenuto di ST(i) e la posizione del TOP
non vengono modificati.
18.2.16 Le istruzioni FICOM e FICOMP - Compare Integer
Queste istruzioni comparano l'operando sorgente con il contenuto di ST(0); il
risultato della comparazione viene codificato nei flags C0, C2 e C3
del Condition Code, presenti nel registro Status Word della FPU.
L'operando destinazione è implicitamente ST(0), mentre l'operando sorgente deve
essere esplicitamente una locazione di memoria contenente un Word Integer o uno
Short Integer.
Prima della comparazione, l'operando sorgente viene caricato nello stack e convertito
in Extended Real. Si tratta di una comparazione di tipo "unordered" tra
numeri interi; il segno dello zero viene ignorato, per cui \({+0.0} = {-0.0}\).
La tabella seguente indica i valori assegnati a C0, C2 e C3 in base
al risultato della comparazione; se almeno uno degli operandi è un NaN o un
formato non supportato, tali tre flags vengono posti tutti a 1 ("unordered").
L'istruzione FICOM esegue la comparazione e modifica di conseguenza i flags del
Condition Code.
L'istruzione FICOMP effettua anche un POP dopo aver comparato i due operandi.
18.2.17 L'istruzione FILD - Load Integer
Questa istruzione richiede esplicitamente l'operando sorgente, che deve essere una
locazione di memoria contenente un Word Integer, uno Short Integer o
un Long Integer; tale operando viene convertito in Extended Real e
poi caricato nella nuova cima dello stack (PUSH). Come sappiamo, questi tre
tipi standard di numero intero vengono convertiti in Extended Real senza
errori di approssimazione.
18.2.18 L'istruzione FINCSTP - Increment Stack-Top Pointer
Questa istruzione incrementa di 1 il puntatore alla cima dello stack (TOP);
l'effetto che si ottiene sui Data Registers della FPU viene illustrato
in modo simbolico dalla Figura 18.4, dove i registri da R0 a R7 si trovano
in una struttura circolare fissa, mentre i puntatori da ST(0) a ST(7)
possono ruotare in senso orario (POP) o in senso antiorario (PUSH).
In questo esempio, si ha TOP=2; la cima dello stack, rappresentata da ST(0),
si trova quindi nel registro R2. L'istruzione FINCSTP ruota sempre i
puntatori in senso orario (POP) di una posizione; di conseguenza, si ottiene
TOP=3, per cui ST(0) punterà a R3, ST(1) a R4 e così via.
Si tenga presente che FINCSTP non equivale ad una estrazione dallo stack; infatti,
in questo caso il contenuto del vecchio ST(0) non viene etichettato come "vuoto"
nella Tag Word.
Chiaramente, se si esegue FINCSTP quando TOP=7, si ottiene TOP=0; si
tratta, come sappiamo, del classico wrap around.
L'esecuzione dell'istruzione FINCSTP non modifica il contenuto dei registri dello
stack e della Tag Word; di conseguenza, anche in caso di wrap around, la
FPU non genera alcuna eccezione.
18.2.19 Le istruzioni FINIT e FNINIT - Initialize Floating-Point Unit
Queste istruzioni inizializzano la FPU riportando ai valori predefiniti i vari
campi dei registri Control Word, Status Word, Tag Word, Instruction
Pointer e Data Pointer. Nel precedente capitolo abbiamo visto che la Control
Word viene posta a 037Fh (round to nearest, tutte le eccezioni mascherate e
precisione della mantissa a 64 bit), la Status Word viene posta a 0000h
(nessuna eccezione in attesa, TOP=0) e la Tag Word viene posta a FFFFh
(tutti i registri dello stack sono etichettati come "vuoti"); l'Instruction Pointer e
il Data Pointer vengono azzerati.
L'istruzione FNINIT si limita ad inizializzare i registri elencati in precedenza;
al contrario, l'istruzione FINIT verifica la presenza di eventuali eccezioni in
attesa e provvede alla loro elaborazione prima di procedere con l'inizializzazione della
FPU. In presenza di una istruzione FINIT, la FPU esegue in sequenza
le due istruzioni FWAIT (vedere più avanti) e FNINIT.
18.2.20 Le istruzioni FIST e FISTP - Store Integer
Queste istruzioni leggono il contenuto di ST(0), lo convertono in formato Word
Integer o Short Integer e lo salvano in una locazione di memoria.
ST(0) è la sorgente implicita, mentre un m16int o un m32int deve
essere indicato esplicitamente come destinazione.
A seconda del contenuto di ST(0), nell'operando destinazione verranno caricati i
seguenti valori, eventualmente arrotondati ad un Word Integer o a uno Short
Integer:
\begin{align}
-\infty &\implies * \cr
F \le -1 &\implies -I \cr
-1 \lt F \lt -0 &\implies ** \cr
-0 &\implies 0 \cr
+0 &\implies 0 \cr
+0 \lt F \lt +1 &\implies ** \cr
F \ge +1 &\implies +I \cr
+\infty &\implies * \cr
NaN &\implies *
\end{align}
F = numero in floating point.
I = numero intero.
\(-\infty\) comprende anche il caso di un numero negativo troppo grande in valore assoluto.
\(+\infty\) comprende anche il caso di un numero positivo troppo grande.
(*) operazione aritmetica non valida (eccezione IE).
(**) \(0\) o \(\pm 1\) a seconda del modo di arrotondamento in uso.
L'istruzione FIST si limita a trasferire in memoria il contenuto di ST(0).
L'istruzione FISTP esegue lo stesso compito di FIST e poi effettua un
POP; inoltre, FISTP è in grado di salvare in memoria anche un numero
intero in formato Long Integer a 64 bit.
Se il contenuto di ST(0) non è un numero intero, prima del trasferimento in memoria
viene effettuato un arrotondamento ad un intero secondo il modo in uso.
In caso di operazione aritmetica non valida, se IE non è mascherato, viene generata
una eccezione e l'operando sorgente non viene copiato nell'operando destinazione; se
IE è mascherato, un Integer Indefinite viene copiato nell'operando
destinazione.
18.2.21 L'istruzione FISTTP - Store Integer with Truncation
Questa istruzione è del tutto simile a FISTP; la differenza fondamentale sta nel
fatto che il contenuto di ST(0) viene sottoposto a troncamento delle cifre dopo
la virgola prima del trasferimento in memoria.
Anche in questo caso, ST(0) è la sorgente implicita; l'operando destinazione deve
essere indicato esplicitamente e può essere un m16int, un m32int o un
m64int.
A seconda del contenuto di ST(0), nell'operando destinazione verranno caricati i
seguenti valori, eventualmente arrotondati ad un Word Integer, Short Integer
o Long Integer tramite troncamento:
\begin{align}
-\infty &\implies * \cr
F \le -1 &\implies -I \cr
-1 \lt F \lt +1 &\implies 0 \cr
F \ge +1 &\implies +I \cr
+\infty &\implies * \cr
NaN &\implies *
\end{align}
F = numero in floating point.
I = numero intero.
\(-\infty\) comprende anche il caso di un numero negativo troppo grande in valore assoluto.
\(+\infty\) comprende anche il caso di un numero positivo troppo grande.
(*) operazione aritmetica non valida (eccezione IE).
In caso di operazione aritmetica non valida, se IE non è mascherato, viene generata
una eccezione e l'operando sorgente non viene copiato nell'operando destinazione; se
IE è mascherato, un Integer Indefinite viene copiato nell'operando
destinazione.
18.2.22 L'istruzione FLD - Load Floating-Point Value
Questa istruzione richiede esplicitamente l'operando sorgente, che deve essere il
contenuto di ST(i), oppure una locazione di memoria contenente un Single
Real, un Double Real o un Extended Real; tale operando viene
convertito in Extended Real (se non lo era già) e poi caricato nella nuova
cima dello stack (PUSH).
L'operando destinazione è implicitamente ST(0); se anche l'operando sorgente
(esplicito) è ST(0), il PUSH non fa altro che creare un duplicato del
contenuto di tale registro.
18.2.23 Le istruzioni FLD1, FLDL2T, FLDL2E, FLDPI, FLDLG2, FLDLN2 e FLDZ
- Load Constant
Queste istruzioni caricano nella nuova cima dello stack (PUSH) una serie di
costanti predefinite a 66 bit, che si trovano in una apposita memoria ROM
della FPU; il caricamento nello stack comporta una conversione di tali costanti
in formato Extended Real, senza che venga generata alcuna eccezione di precisione
(PE).
Non è richiesto alcun operando esplicito; ST(0) viene usato come DEST
implicito.
Le costanti disponibili sono le seguenti (e rappresenta il Numero di Nepero
\(2.71828182845904523536 \dots\)):
- FLD1: carica \(+1.0\) in ST(0)
- FLDL2T: carica \(\log_2 {10}\) in ST(0)
- FLDL2E: carica \(\log_2 {e}\) in ST(0)
- FLDPI: carica \(\pi\) in ST(0)
- FLDLG2: carica \(\log_{10} {2}\) in ST(0)
- FLDLN2: carica \(\log_e {2}\) in ST(0)
- FLDZ: carica \(+0.0\) in ST(0)
18.2.24 L'istruzione FLDCW - Load x87 FPU Control Word
Questa istruzione richiede esplicitamente come operando sorgente una locazione di memoria
da 16 bit; il contenuto di tale locazione viene copiato nel registro Control
Word della FPU. Chiaramente, questa istruzione ha lo scopo di modificare la
modalità operativa della FPU; a tale proposito, si veda la Figura 17.7 del
Capitolo 17.
Per evitare problemi legati ad eventuali eccezioni ancora da elaborare, è vivamente
consigliato eseguire l'istruzione FCLEX (o FNCLEX) prima di FLDCW.
18.2.25 L'istruzione FLDENV - Load x87 FPU Environment
Questa istruzione richiede esplicitamente come operando sorgente l'indirizzo di
una locazione di memoria da 14 o 28 byte; il contenuto di tale locazione
rappresenta il completo ambiente operativo della FPU e viene salvato nei registri
Control Word, Status Word, Tag Word, Instruction Pointer,
Data Pointer e Last Opcode.
Il blocco di memoria contenente il completo ambiente operativo della FPU, deve
assumere una struttura ben precisa; a tale proposito, si vedano le Figure 18.10 e 18.11
relative all'istruzione FSTENV/FNSTENV.
Come è stato già spiegato in precedenza per l'istruzione FLDCW, per evitare
problemi legati ad eventuali eccezioni ancora da elaborare, è vivamente consigliato
eseguire l'istruzione FCLEX (o FNCLEX) prima di FLDENV.
18.2.26 Le istruzioni FMUL, FMULP e FIMUL - Multiply
Queste istruzioni moltiplicano l'operando destinazione per l'operando sorgente e
salvano il risultato nell'operando destinazione; si ha quindi:
\[ {DEST} = {{DEST} \cdot {SRC}} \]
L'operando destinazione deve essere obbligatoriamente un registro dello stack.
L'operando sorgente può essere un registro dello stack oppure una locazione di memoria
contenente un Single Real, un Double Real, un Word Integer o uno
Short Integer; se si intende eseguire la moltiplicazione tra due Extended
Real, bisogna usare due registri dello stack.
In assenza di operandi espliciti, vengono usati implicitamente ST(0) e
ST(1) come fattori; il risultato viene salvato in ST(1). In presenza di
un solo operando esplicito, che deve essere una locazione di memoria, il contenuto di
tale locazione viene moltiplicato per l'operando implicito ST(0) e il risultato
viene salvato nello stesso ST(0). In alternativa, si possono usare esplicitamente
i due operandi ST(0) e ST(i), con il risultato che viene salvato
nell'operando che si trova più a sinistra (destinazione); è anche possibile quindi
moltiplicare ST(0) per se stesso (per ottenere [ST(0)]2)
scrivendo (sintassi MASM):
fmul ST(0), ST(0)
L'istruzione FMUL esegue la moltiplicazione e salva il risultato nell'operando
destinazione dello stack, sovrascrivendo il vecchio contenuto.
FMULP esegue la moltiplicazione, salva il risultato nell'operando destinazione e
effettua un POP.
FIMUL richiede un intero come operando sorgente; tale operando viene convertito
in Extended Real prima di essere moltiplicato per l'operando destinazione.
Il segno del prodotto segue le solite regole anche quando sono presenti fattori del tipo
\({\pm 0}\) o \({\pm \infty}\); per ottenere il segno del prodotto, la FPU esegue
uno XOR tra il bit di segno di SRC e quello di DEST.
La tabella seguente illustra il risultato che si ottiene moltiplicando tra loro vari tipi di
numeri nel caso in cui non si verifichino overflow o underflow; F indica un numero
in floating point, I un numero intero, SRC l'operando sorgente e DEST
l'operando destinazione.
(*) Eccezione di operazione aritmetica non valida (IE). Forme indeterminate
\(({\pm \infty}) \cdot ({\pm 0})\) o \(({\pm 0}) \cdot ({\pm \infty})\).
18.2.27 L'istruzione FNOP - No Operation
Questa istruzione ha unicamente il compito di occupare spazio nel flusso di istruzioni del
programma in esecuzione; in presenza di tale istruzione, il contenuto dell'Instruction
Pointer (IP/EIP) della CPU e di quello della FPU viene incrementato
di 2 byte (pari alla dimensione dell'opcode D9h D0h di FNOP).
18.2.28 L'istruzione FPATAN - Partial Arctangent
Questa istruzione calcola l'angolo (in radianti) la cui tangente è data dal rapporto
ST(1)/ST(0), salva il risultato in ST(1) ed effettua un POP; in
seguito al POP, il risultato si viene a trovare nel nuovo ST(0).
Non è richiesto alcun operando esplicito; i due registri ST(0) e ST(1)
vengono utilizzati come operandi impliciti.
Per chiarire meglio il funzionamento di questa istruzione, possiamo servirci della
Figura 18.5
Come sappiamo dalla matematica, dato il triangolo rettangolo \(\widehat{OAP}\),
rettangolo in \(A\), si definisce tangente dell'angolo \(\alpha\) il
rapporto tra il cateto opposto e il cateto adiacente all'angolo stesso; quindi,
per il punto \(P(x,y)\) di Figura 18.5 si ha:
\[ t = \tan(\alpha) = {y \over x} \]
Come viene illustrato dalla Figura 18.5, FPATAN si aspetta che \(x\) si trovi
in ST(0) e \(y\) in ST(1).
La funzione \(t = \tan(\alpha)\) quindi è definita in tutto \(\Bbb R\), esclusi i
punti di discontinuità \(\alpha = (\pi/2 + k\pi)\), con \(k \in \Bbb Z\), dove la
\(x\) si annulla; infatti, tenendo fissa la distanza \(\overline{OP}\) e facendo
ruotare in senso antiorario il punto \(P\), si può vedere come varia il valore \(t\)
restituito da \(\tan(\alpha)\).
Nell'intervallo \(-\pi/2 \lt \alpha \lt +\pi/2\), la funzione \(t = \tan(\alpha)\) è
monotòna crescente, per cui ammette la funzione inversa \(\alpha = \arctan(t)\), la
quale ci restituisce l'angolo \(\alpha\) la cui tangente è \(t\); quindi, qualunque
sia \(t \in \Bbb R\), la funzione \(\arctan(t)\) ci restituisce
\(-\pi/2 \lt \alpha \lt +\pi/2\). La Figura 18.6 illustra tutti questi aspetti (la
variabile indipendente viene indicata con α per entrambe le funzioni).
La tabella seguente illustra il risultato che si ottiene in base ai valori contenuti in
ST(0) e ST(1) nel caso in cui non si verifichi un underflow; F
indica un numero in floating point.
Bisogna prestare attenzione al fatto che il risultato di FPATAN dipende dai
segni di \(x\) e \(y\) presi separatamente e non dal segno di \({x} / {y}\); a tale
proposito, si osservino i segni di \(x\) e \(y\) nei quattro quadranti di Figura 18.5.
Si noti che il rapporto \(y/x\) può dare luogo alle forme \(({\pm \infty} / {\pm \infty})\)
e \(({\pm 0} / {\pm 0})\), che in questo caso non vengono considerate come indeterminate;
infatti, la FPU esegue FPATAN utilizzando appositi algoritmi che accettano
come argomento dell'arcotangente anche numeri complessi. Nel campo numerico complesso,
\(\arctan({\pm \infty} / {\pm \infty})\) e \(\arctan({\pm 0} / {\pm 0})\) assumono valori
ben definiti.
18.2.29 L'istruzione FPREM - Partial Remainder
Questa istruzione calcola la divisione ST(0)/ST(1) e salva il resto in ST(0).
Non è richiesto alcun operando esplicito; i due registri ST(0) e ST(1)
vengono utilizzati come operandi impliciti.
Come sappiamo, il resto di una divisione intera è sempre inferiore (in valore assoluto) al
divisore e il suo segno è lo stesso del dividendo. Dati il dividendo \(A\), il divisore
\(B\), il quoziente \(Q\) e il resto \(R\), si ha:
\[ A = Q \cdot B + R \implies R = A - (Q \cdot B) \]
FPREM calcola il resto \(R\) trasformando prima \(Q\) in un numero intero tramite
troncamento della parte decimale (il metodo di arrotondamento in uso viene ignorato); il
risultato ottenuto è sempre esatto, per cui nessuna eccezione di precisione viene generata.
La tabella seguente illustra il risultato che si ottiene in base ai valori contenuti in
ST(0) e ST(1) nel caso in cui non si verifichi un underflow; F
indica un numero in floating point.
(*) Forme indeterminate \(({\pm \infty} / {\pm \infty})\) e \(({\pm 0} / {\pm 0})\)
e altre eccezioni di operazione aritmetica non valida (IE).
(**) Eccezione di divisione per zero (ZE).
Così come la moltiplicazione può essere eseguita come una sequenza di addizioni, anche
la divisione può essere scomposta in una serie di sottrazioni; l'istruzione FPREM
utilizza proprio tale metodo, trattando ST(0) e ST(1) come numeri reali.
Ogni sottrazione riduce la caratteristica del dividendo ST(0); nel caso di
FPREM, tale riduzione non può andare oltre 63, per cui può capitare di
ottenere un resto che, in valore assoluto, non è minore del divisore. Se il resto
ottenuto è inferiore al divisore, la riduzione è completa e ciò viene segnalato ponendo
C2=0; in caso contrario si ha C2=1 e si parla allora di partial
remainder (da cui il nome dell'istruzione). Se la riduzione è completa, il tre bit
meno significativi del quoziente vengono salvati in C3, C1 e C0.
Se si ottiene C2=1, è possibile rieseguire FPREM utilizzando come nuovo
dividendo il resto parziale salvato in ST(0); tale iterazione può continuare
finché non si ha C2=0. Si tratta di una tecnica molto utile in varie situazioni
come, ad esempio, la riduzione dell'argomento di funzioni periodiche; nel tutoria
Assembly Base abbiamo visto un'altra applicazione di tale metodo per convertire
un numero intero da base 10 a base 2.
18.2.30 L'istruzione FPREM1 - Partial Remainder
Questa istruzione è del tutto simile a FPREM; l'unica differenza è che il resto
della divisione ST(0)/ST(1) viene calcolato in base allo standard IEEE 754,
che consiste nell'arrotondare il quoziente di ST(0)/ST(1) all'intero più vicino.
La tabella seguente illustra il risultato che si ottiene in base ai valori contenuti in
ST(0) e ST(1) nel caso in cui non si verifichi un underflow; F
indica un numero in floating point.
(*) Forme indeterminate \(({\pm \infty} / {\pm \infty})\) e \(({\pm 0} / {\pm 0})\)
e altre eccezioni di operazione aritmetica non valida (IE).
(**) Eccezione di divisione per zero (ZE).
18.2.31 L'istruzione FPTAN - Partial Tangent
Questa istruzione legge il contenuto \(x\) di ST(0), calcola \(\tan(x)\), salva
il risultato nello stesso ST(0) e infine inserisce nello stack (PUSH) il
valore \(1.0\).
Non è richiesto alcun operando esplicito; ST(0) e ST(1) vengono usati come
operandi impliciti.
Come sappiamo dalla matematica, la funzione \(\tan(x)\) è periodica con periodo \(\pi\)
ed è definita in tutto \(\Bbb R\), esclusi i punti di discontinuità \(x = \pi/2 + k\pi\),
con \(k \in \Bbb Z\), dove tende a \(\infty\); il grafico di \(\tan(x)\) è visibile in
Figura 18.6.
Affinché FPTAN produca un risultato valido, \(x\) deve essere espresso in radianti
e si deve avere \(-2^{63} \lt x \lt +2^{63}\); in caso contrario, la FPU non
esegue il calcolo di \(\tan(x)\) e il contenuto di ST(0) resta quindi invariato.
Se \(x\) è un valore valido e non è un punto di discontinuità, si ottengono i seguenti
risultati (F indica un numero in floating point):
\begin{align}
x = -\infty &\implies \tan(x) = * \cr
x = -F &\implies -F \le \tan(x) \le +F \cr
x = -0 &\implies \tan(x) = -0 \cr
x = +0 &\implies \tan(x) = +0 \cr
x = +F &\implies -F \le \tan(x) \le +F \cr
x = +\infty &\implies \tan(x) = * \cr
x = NaN &\implies \tan(x) = NaN
\end{align}
(*) operazione aritmetica non valida (eccezione IE).
Nel caso in cui \(x\), pur essendo un Extended Real valido, non rientri però nei
limiti esposti in precedenza, FPTAN non genera alcuna eccezione di operazione
aritmetica non valida, ma si limita a porre C2=1; è compito dei programmi quindi
testare lo stato di questo flag del Condition Code.
Per evitare grossolani errori di approssimazione, si consiglia di usare FPTAN
con \(-(3\pi)/8 \lt x \lt +(3\pi)/8\).
La ragione per cui l'esecuzione di FPTAN si conclude con un PUSH del
valore \(1.0\) nello stack sta nel fatto che, in questo modo, si può facilmente calcolare
\(\cot(x)\) la quale, come sappiamo, è data da \(1/\tan(x)\); osservando allora che
dopo il PUSH si ha il valore \(1.0\) in ST(0) e il valore \(\tan(x)\) in
ST(1), per ottenere \(\cot(x)\) basta eseguire l'istruzione FDIVRP subito
dopo FPTAN.
18.2.32 L'istruzione FRNDINT - Round to Integer
Questa istruzione legge il contenuto di ST(0), lo approssima ad un intero in
base al modo di approssimazione in uso e salva il risultato nello stesso ST(0).
Non è richiesto alcun operando esplicito; ST(0) viene usato implicitamente
come sorgente e come destinazione.
Se ST(0) contiene \({\pm \infty}\), non viene effettuata alcuna approssimazione;
il contenuto dello stesso ST(0) rimane quindi invariato.
Se FRNDINT non è in grado di produrre un risultato intero, viene generata
l'eccezione di precisione (PE).
18.2.33 L'istruzione FRSTOR - Restore x87 FPU State
Questa istruzione richiede esplicitamente come operando sorgente l'indirizzo
di una locazione di memoria da 94 o 108 byte; il contenuto di tale
locazione rappresenta il completo ambiente operativo della FPU (compreso lo
stack) e viene salvato nei registri Control Word, Status Word, Tag
Word, Instruction Pointer, Data Pointer, Last Opcode e
Stack.
Il blocco di memoria contenente il completo ambiente operativo della FPU, deve
assumere una struttura ben precisa; a tale proposito, si vedano le Figure 18.7 e 18.8
relative all'istruzione FSAVE/FNSAVE.
Se FRSTOR pone a 1 uno o più bit di eccezione nella Status Word,
viene generata una eccezione della FPU; per evitare problemi di questo genere,
è vivamente consigliato eseguire l'istruzione FCLEX (o FNCLEX) prima di
FRSTOR.
18.2.34 Le istruzioni FSAVE e FNSAVE - Store x87 FPU State
Questa istruzione richiede esplicitamente come operando destinazione l'indirizzo
di una locazione di memoria da 94 o 108 byte; in tale locazione viene
salvato il completo ambiente operativo della FPU (compreso lo stack), letto
dal contenuto corrente dei registri Control Word, Status Word, Tag
Word, Instruction Pointer, Data Pointer, Last Opcode e
Stack. Dopo il salvataggio di queste informazioni, viene effettuata una
reinizializzazione della FPU, come descritto in precedenza per le istruzioni
FINIT/FNINIT.
In modalità reale a 16 bit, i dati salvati in memoria assumono la struttura
mostrata in Figura 18.7.
In modalità reale a 32 bit, i dati salvati in memoria assumono la struttura
mostrata in Figura 18.8.
FIP indica l'Instruction Pointer, FOP il Last Opcode,
e FDP il Data Pointer; il contenuto dello stack viene salvato in 8
locazioni da 10 byte ciascuna, poste immediatamente dopo le strutture di Figura
18.7 e 18.8.
L'istruzione FNSAVE si limita ad effettuare il salvataggio in memoria delle
informazioni elencate in precedenza; al contrario, l'istruzione FSAVE verifica
la presenza di eventuali eccezioni in attesa e provvede alla loro elaborazione prima di
svolgere lo stesso compito di FNSAVE.
In presenza di una istruzione FSAVE, la FPU esegue in sequenza le due
istruzioni FWAIT (vedere più avanti) e FNSAVE.
18.2.35 L'istruzione FSCALE - Scale
Questa istruzione legge il contenuto dell'operando sorgente ST(1), lo trasforma
in un intero troncando la sua parte decimale, lo somma alla caratteristica dell'operando
destinazione ST(0) e salva il risultato nello stesso ST(0).
Non è richiesto alcun operando esplicito; l'operando sorgente implicito è ST(1),
mentre l'operando destinazione implicito è ST(0).
L'istruzione FSCALE è così chiamata in quanto scala ST(0) del fattore
2ST(1); se ST(0) è il numero reale \((m_1 \cdot 2^{e_1})\) e
ST(1) è il numero intero \(i_2\) (da sommare a \(e_1\)), si ha infatti:
\[ m_1 \cdot 2^{(e_1 + i_2)} = m_1 \cdot (2^{e_1} \cdot 2^{i_2}) =
(m_1 \cdot 2^{e_1}) \cdot 2^{i_2} = ST(0) \cdot 2^{ST(1)} \]
In pratica, FSCALE viene utilizzata per moltiplicare o dividere rapidamente un
numero reale per una potenza intera di \(2\); infatti, si osservi che se ST(1)
è un numero positivo, si sta moltiplicando ST(0) per 2ST(1),
mentre se ST(1) è un numero negativo, si sta dividendo ST(0) per
2ST(1).
La tabella seguente illustra il risultato che si ottiene applicando FSCALE a vari
tipi di numeri nel caso in cui non si verifichino overflow o underflow; F indica
un numero in floating point.
Generalmente, FSCALE modifica solo la caratteristica di ST(0), mentre la
mantissa rimane invariata; se però ST(0) contiene un numero reale denormalizzato,
anche la mantissa viene modificata. Lo stesso può verificarsi anche in caso di overflow
o underflow.
18.2.36 L'istruzione FSIN - Sine
Questa istruzione legge il contenuto \(x\) di ST(0), calcola \(\sin(x)\) e salva
il risultato nello stesso ST(0).
Non è richiesto alcun operando esplicito; l'operando ST(0) viene usato
implicitamente come sorgente e come destinazione.
Come sappiamo dalla matematica, la funzione \(\sin(x)\) è periodica con periodo \(2\pi\),
è definita in tutto \(\Bbb R\) e \(\forall \ x \in \Bbb R\) si ha \(-1 \le \sin(x) \le +1\);
la Figura 18.9 illustra tutti questi aspetti.
Affinché FSIN produca un risultato valido, \(x\) deve essere espresso in radianti
e si deve avere \(-2^{63} \lt x \lt +2^{63}\); in caso contrario, la FPU non
esegue il calcolo di \(\sin(x)\) e il contenuto di ST(0) resta quindi invariato.
Se \(x\) è un valore valido, si ottengono i seguenti risultati (F indica un numero
in floating point):
\begin{align}
x = -\infty &\implies \sin(x) = * \cr
x = -F &\implies -1 \le \sin(x) \le +1 \cr
x = -0 &\implies \sin(x) = -0 \cr
x = +0 &\implies \sin(x) = +0 \cr
x = +F &\implies -1 \le \sin(x) \le +1 \cr
x = +\infty &\implies \sin(x) = * \cr
x = NaN &\implies \sin(x) = NaN
\end{align}
(*) operazione aritmetica non valida (eccezione IE).
Nel caso in cui \(x\), pur essendo un Extended Real valido, non rientri però nei
limiti esposti in precedenza, FSIN non genera alcuna eccezione di operazione
aritmetica non valida, ma si limita a porre C2=1; è compito dei programmi quindi
testare lo stato di questo flag del Condition Code.
Per evitare grossolani errori di approssimazione, si consiglia di usare FSIN
con \(-(3\pi)/4 \lt x \lt +(3\pi)/4\).
18.2.37 L'istruzione FSINCOS - Sine and Cosine
Questa istruzione legge il contenuto \(x\) di ST(0), calcola \(\sin(x)\) e
\(\cos(x)\), salva \(\sin(x)\) in ST(0), effettua un PUSH e salva
\(\cos(x)\) nel nuovo ST(0); in questo modo, \(\cos(x)\) si viene a trovare in
ST(0) e \(\sin(x)\) in ST(1).
L'istruzione FSINCOS viene eseguita dalla FPU più velocemente delle due
istruzioni FSIN e FCOS in sequenza.
Non è richiesto alcun operando esplicito; i due registri ST(0) e ST(1)
vengono usati come operandi impliciti.
Affinché FSINCOS produca un risultato valido, \(x\) deve essere espresso in
radianti e si deve avere \(-2^{63} \lt x \lt +2^{63}\); in caso contrario, la FPU
non esegue il calcolo di \(\sin(x)\) e \(\cos(x)\) e il contenuto di ST(0) e
ST(1) resta quindi invariato. Se \(x\) è un valore valido, si ottengono i seguenti
risultati (F indica un numero in floating point):
\begin{align}
x = -\infty &\implies \sin(x) = *,\ \cos(x) = * \cr
x = -F &\implies -1 \le \sin(x) \le +1,\ -1 \le \cos(x) \le +1, \cr
x = -0 &\implies \sin(x) = -0,\ \cos(x) = +1 \cr
x = +0 &\implies \sin(x) = +0,\ \cos(x) = +1 \cr
x = +F &\implies -1 \le \sin(x) \le +1,\ -1 \le \cos(x) \le +1, \cr
x = +\infty &\implies \sin(x) = *,\ \cos(x) = * \cr
x = NaN &\implies \sin(x) = NaN,\ \cos(x) = NaN
\end{align}
(*) operazione aritmetica non valida (eccezione IE).
Nel caso in cui \(x\), pur essendo un Extended Real valido, non rientri però nei
limiti esposti in precedenza, FSINCOS non genera alcuna eccezione di operazione
aritmetica non valida, ma si limita a porre C2=1; è compito dei programmi quindi
testare lo stato di questo flag del Condition Code.
Per evitare grossolani errori di approssimazione, si consiglia di usare FSINCOS
con \(-(3\pi)/8 \lt x \lt +(3\pi)/8\).
18.2.38 L'istruzione FSQRT - Square Root
Questa istruzione legge il contenuto \(x\) di ST(0), calcola \(\sqrt{x}\) e
salva il risultato nello stesso ST(0).
Non è richiesto alcun operando esplicito; l'operando ST(0) viene usato
implicitamente come sorgente e come destinazione.
Come sappiamo dalla matematica, la funzione \(\sqrt{x}\) è definita in tutto
\(\Bbb R^+ \cup 0\); per valori negativi della \(x\) si ottiene un numero complesso.
La seguente tabella mostra il risultato che si ottiene applicando FSQRT a vari
tipi di numeri nel caso in cui non si verifichi un underflow o un overflow (F
indica un numero in floating point):
\begin{align}
x = -\infty &\implies \sqrt{x} = * \cr
x = -F &\implies \sqrt{x} = * \cr
x = -0 &\implies \sqrt{x} = -0 \cr
x = +0 &\implies \sqrt{x} = +0 \cr
x = +F &\implies \sqrt{x} = +F \cr
x = +\infty &\implies \sqrt{x} = +\infty \cr
x = NaN &\implies \sqrt{x} = NaN
\end{align}
(*) operazione aritmetica non valida (eccezione IE).
-0.0 viene considerato equivalente a +0.0 e quindi non negativo.
18.2.39 Le istruzioni FST e FSTP - Store Floating-Point Value
Queste istruzioni leggono il numero reale in Extended Real contenuto di ST(0)
e lo salvano in memoria in formato Single Real o Double Real.
Il registro ST(0) è la sorgente implicita, mentre un m32fp o m64fp
devono essere indicati esplicitamente come destinazione. In alternativa, è anche possibile
indicare ST(i) come destinazione esplicita.
L'istruzione FST si limita a trasferire in memoria il contenuto di ST(0).
L'istruzione FSTP esegue lo stesso compito di FST e poi effettua un
POP; inoltre, se richiesto FSTP può anche salvare un Extended Real
in una locazione di memoria da 80 bit (in tal caso, bisogna specificare come
destinazione un m80fp).
Se l'operando destinazione è un Single Real o un Double Real, l'Extended
Real contenuto nella sorgente ST(0) viene arrotondato a 32 o 64
bit, rispettivamente, secondo il modo di arrotondamento in uso.
Se il dato da salvare in memoria è troppo grande in valore assoluto, viene generata una
eccezione di overflow (OE); se il relativo flag non è mascherato, tale dato non
viene trasferito nell'operando destinazione. Se il dato da salvare in memoria è un numero
denormalizzato, non viene generata alcuna eccezione DE; al suo posto, la
FPU genera una eccezione di underflow (UE).
Queste istruzioni sono in grado di salvare nell'operando destinazione anche \(\pm 0\)
e \(\pm\infty\); eventualmente, la codifica di tali valori viene troncata in modo da
rispettare l'ampiezza in bit di DEST.
Se l'operando destinazione è ST(i) e tale registro non è vuoto, non viene
generata alcuna eccezione di operazione aritmetica non valida (IE).
18.2.40 Le istruzioni FSTCW e FNSTCW - Store x87 FPU Control Word
Queste istruzioni leggono il contenuto della Control Word della FPU e lo
salvano in una locazione di memoria da 16 bit.
A differenza di FNSTCW, l'istruzione FSTCW verifica la presenza di
eventuali eccezioni non mascherate e chiama il relativo Exception Handler prima
di svolgere il proprio compito; la FPU esegue FSTCW chiamando in sequenza
le due istruzioni FWAIT (vedere più avanti) e FNSTCW.
18.2.41 Le istruzioni FSTENV e FNSTENV - Store x87 FPU Environment
Queste istruzioni richiedono esplicitamente come operando destinazione l'indirizzo
di una locazione di memoria da 14 o 28 byte; in tale locazione viene
salvato il completo ambiente operativo della FPU (escluso lo stack), letto
dal contenuto corrente dei registri Control Word, Status Word, Tag
Word, Instruction Pointer, Data Pointer, Last Opcode. Dopo
il salvataggio di queste informazioni, tutte le eccezioni della FPU vengono
mascherate nel registro Control Word.
In modalità reale a 16 bit, i dati salvati in memoria assumono la struttura
mostrata in Figura 18.10.
In modalità reale a 32 bit, i dati salvati in memoria assumono la struttura
mostrata in Figura 18.11.
FIP indica l'Instruction Pointer, FOP il Last Opcode,
e FDP il Data Pointer.
L'istruzione FNSTENV si limita ad effettuare il salvataggio in memoria delle
informazioni elencate in precedenza; al contrario, l'istruzione FSTENV verifica
la presenza di eventuali eccezioni in attesa e provvede alla loro elaborazione prima di
svolgere lo stesso compito di FNSAVE.
In presenza di una istruzione FSTENV, la FPU esegue in sequenza le due
istruzioni FWAIT (vedere più avanti) e FNSTENV.
18.2.42 Le istruzioni FSTSW e FNSTSW - Store x87 FPU Status Word
Queste istruzioni leggono il contenuto della Status Word della FPU e lo
salvano in una locazione di memoria da 16 bit o nel registro AX della
CPU; come viene chiarito in seguito, le informazioni salvate in AX
vengono utilizzate per gestire i salti condizionati.
L'operando destinazione deve essere indicato in modo esplicito; l'operando sorgente
implicito è la Status Word.
A differenza di FNSTSW, l'istruzione FSTSW verifica la presenza di
eventuali eccezioni non mascherate e chiama il relativo Exception Handler prima
di svolgere il proprio compito; la FPU esegue FSTSW chiamando in sequenza
le due istruzioni FWAIT (vedere più avanti) e FNSTSW.
Abbiamo visto in questo capitolo che diverse istruzioni della FPU, in particolare,
le istruzioni di comparazione, producono un risultato che provoca anche la modifica dei
bit del Condition Code nella Status Word; il contenuto di tali bit può
essere poi utilizzato per gestire dei salti condizionati attraverso la CPU.
Il metodo da seguire è stato già illustrato nel precedente capitolo; in pratica, i bit
C0, C2, C3 del byte più significativo della Status Word
hanno una disposizione che coincide con quella dei flags, rispettivamente, CF,
PF, ZF nel byte meno significativo del registro FLAGS/EFLAGS della
CPU, come si vede in Figura 18.12.
Utilizzando FSTSW o FNSTSW dopo una comparazione effettuata dalla
FPU, possiamo salvare la Status Word in AX; con l'istruzione
SAHF della CPU copiamo ora il byte più significativo (AH) di
AX nel byte meno significativo del registro FLAGS/EFLAGS. A questo
punto, usiamo la CPU per effettuare salti condizionati in base al risultato
della comparazione stessa (si veda il Capitolo 20 del tutorial Assembly Base).
18.2.43 Le istruzioni FSUB, FSUBP e FISUB - Subtract
Queste istruzioni sottraggono l'operando sorgente dall'operando destinazione e salvano
il risultato nell'operando destinazione; si ha quindi:
\[ {DEST} = {DEST} - {SRC} \]
L'operando destinazione deve essere obbligatoriamente un registro dello stack. Se
l'operando sorgente si trova in memoria, esso può essere un Single Real, un
Double Real, un Word Integer o uno Short Integer; se si intende
eseguire la sottrazione tra due Extended Real, bisogna usare due registri
dello stack.
In assenza di operandi espliciti, vengono usati implicitamente ST(0) come
sorgente e ST(1) come destinazione. In presenza di un solo operando esplicito
(che ricopre il ruolo di sorgente), viene usato implicitamente ST(0) come
destinazione. In alternativa, si può usare esplicitamente ST(i) come sorgente
e ST(0) come destinazione o viceversa; è anche possibile quindi sottrarre
ST(0) da se stesso (per ottenere ST(0)=0) scrivendo (sintassi MASM):
fsub ST(0), ST(0)
L'istruzione FSUB esegue la sottrazione e salva il risultato nell'operando
destinazione dello stack, sovrascrivendo il vecchio contenuto.
FSUBP esegue la sottrazione, salva il risultato nell'operando destinazione e
effettua un POP.
FISUB richiede un intero come operando sorgente; tale operando viene convertito
in Extended Real prima di essere sottratto dall'operando destinazione.
La tabella seguente illustra il risultato che si ottiene eseguendo la sottrazione tra
vari tipi di numeri nel caso in cui non si verifichino overflow o underflow; F
indica un numero in floating point, I un numero intero, SRC l'operando
sorgente e DEST l'operando destinazione.
(*) Eccezione di operazione aritmetica non valida. Forme indeterminate
\(({+\infty}) - ({+\infty})\) o \(({-\infty}) - ({-\infty})\).
18.2.44 Le istruzioni FSUBR, FSUBRP e FISUBR - Reverse Subtract
Queste istruzioni sottraggono l'operando destinazione dall'operando sorgente e salvano
il risultato nell'operando destinazione; si ha quindi:
\[ {DEST} = {SRC} - {DEST} \]
L'operando destinazione deve essere obbligatoriamente un registro dello stack. Se
l'operando sorgente si trova in memoria, esso può essere un Single Real, un
Double Real, un Word Integer o uno Short Integer; se si intende
eseguire la sottrazione tra due Extended Real, bisogna usare due registri
dello stack.
In assenza di operandi espliciti, vengono usati implicitamente ST(0) come
sorgente e ST(1) come destinazione. In presenza di un solo operando esplicito
(che ricopre il ruolo di sorgente), viene usato implicitamente ST(0) come
destinazione. In alternativa, si può usare esplicitamente ST(i) come sorgente
e ST(0) come destinazione o viceversa; è anche possibile quindi sottrarre
ST(0) da se stesso (per ottenere ST(0)=0) scrivendo (sintassi MASM):
fsubr ST(0), ST(0)
L'istruzione FSUBR esegue la sottrazione e salva il risultato nell'operando
destinazione dello stack, sovrascrivendo il vecchio contenuto.
FSUBRP esegue la sottrazione, salva il risultato nell'operando destinazione e
effettua un POP.
FISUBR richiede un intero come operando sorgente; tale operando viene convertito
in Extended Real prima di essere usato nella sottrazione.
La tabella seguente illustra il risultato che si ottiene eseguendo la sottrazione tra
vari tipi di numeri nel caso in cui non si verifichino overflow o underflow; F
indica un numero in floating point, I un numero intero, SRC l'operando
sorgente e DEST l'operando destinazione.
(*) Eccezione di operazione aritmetica non valida. Forme indeterminate
\(({+\infty}) - ({+\infty})\) o \(({-\infty}) - ({-\infty})\).
18.2.45 L'istruzione FTST - Test
Questa istruzione compara il contenuto di ST(0) con il valore \(0.0\) in Extended
Real e modifica i flags C0, C2, C3 in base al risultato.
Non è richiesto alcun operando esplicito; l'operando ST(0) viene usato in modo
implicito.
Il segno dello zero viene ignorato, per cui \({-0.0} = {+0.0}\). La tabella seguente
illustra tutti i dettagli relativi alla comparazione.
La comparazione è di tipo unordered; in pratica, FTST verifica anche il tipo
di numeri da confrontare.
Se ST(0) contiene un NaN o un valore non definito, il risultato della
comparazione è unordered (111b); in tal caso, se il flag IE non è
mascherato, viene generata un'eccezione di operazione aritmetica non valida.
18.2.46 Le istruzioni FUCOM, FUCOMP e FUCOMPP - Unordered Compare
Floating-Point Values
Queste istruzioni comparano il contenuto di ST(0) con quello di ST(i); il
risultato della comparazione viene codificato nei flags C0, C2 e C3
del Condition Code, presenti nel registro Status Word della FPU.
In assenza di operandi espliciti, vengono comparati i contenuti di ST(0) e
ST(1); in alternativa, è possibile indicare esplicitamente l'operando ST(i),
il cui contenuto viene comparato con quello dell'operando implicito ST(0).
La comparazione è di tipo unordered tra numeri in floating point; il segno dello
zero viene ignorato, per cui \({-0.0} = {+0.0}\). La tabella seguente illustra tutti i
dettagli relativi alla comparazione.
Queste istruzioni sono del tutto simili a FCOM/FCOMP/FCOMPP; la differenza sta
nel fatto che FUCOM/FUCOMP/FUCOMPP generano una eccezione di operazione
aritmetica non valida solo quando uno o entrambi gli operandi sono di tipo sNaN
o in formato non supportato. Se nella comparazione è coinvolto almeno un qNaN,
il risultato è unordered, ma nessuna eccezione viene generata. Se si verifica
una eccezione di operazione aritmetica non valida e il flag IE non è mascherato,
i flags del Condition Code non vengono modificati.
L'istruzione FUCOMP effettua un POP dopo aver comparato i due operandi.
L'istruzione FUCOMPP effettua due POP dopo aver comparato i due operandi.
18.2.47 L'istruzione FWAIT - Wait
Questa istruzione è identica (stesso opcode) alla WAIT della CPU; il suo
scopo è quello di imporre alla FPU di verificare la presenza di eccezioni in
attesa ed eventualmente consentire la loro elaborazione prima di procedere con
l'istruzione successiva.
Non è richiesto alcun operando implicito o esplicito.
FWAIT è molto utile in presenza di una generica istruzione Fxxx la quale
produce eccezioni non mascherate che vogliamo assolutamente gestire; in tal caso, non
dobbiamo fare altro che inserire appunto una FWAIT subito dopo la stessa
Fxxx.
Nel capitolo precedente è stato spiegato che FWAIT è necessaria con le vecchie
FPU 8087 quando vogliamo che un'istruzione numerica concluda il proprio lavoro
(ad esempio, il calcolo di una radice quadrata) prima che la CPU inizi ad
elaborare l'istruzione successiva; con le moderne CPU, dotate di FPU
integrata, ciò non è più necessario in quanto tale modo di procedere viene gestito
automaticamente via hardware.
18.2.48 L'istruzione FXAM - Examine Floating-Point
Questa istruzione esamina il contenuto di ST(0) e modifica i flags C0,
C2, C3 in base al tipo di dato riscontrato.
Non è richiesto alcun operando esplicito; l'operando ST(0) viene usato in modo
implicito.
La tabella seguente illustra i possibili risultati prodotti da FXAM.
Se ST(0) non è vuoto, il flag C1 contiene l'eventuale segno del tipo di dato.
18.2.49 L'istruzione FXCH - Exchange Register Content
Questa istruzione scambia il contenuto di ST(0) con quello di ST(i).
In presenza del solo operando esplicito ST(i), viene usato implicitamente
ST(0) per lo scambio. In assenza di operandi espliciti, vengono usati
implicitamente ST(0) e ST(1) per lo scambio.
Questa istruzione è molto utile quando si deve spostare sulla cima dello stack un
operando su cui si devono eseguire determinate operazioni; ad esempio, dato che
FSQRT usa ST(0) come operando implicito, per calcolare la radice
quadrata del contenuto di ST(3) si può ricorrere alla seguente serie di
istruzioni (sintassi MASM):
18.2.50 L'istruzione FXRSTOR - Restore x87 FPU, MMX, XMM and MXCSR State
Questa istruzione è rivolta alle nuove funzionalità disponibili con le CPU di
classe Pentium II o superiore.
18.2.51 L'istruzione FXSAVE - Save x87 FPU, MMX Technology and SSE State
Questa istruzione è rivolta alle nuove funzionalità disponibili con le CPU di
classe Pentium II o superiore.
18.2.52 L'istruzione FXTRACT - Extract Exponent and Significand
Questa istruzione legge il contenuto di ST(0), ne ricava la caratteristica
(Exponent) e la mantissa (Significand), salva la caratteristica in
ST(0) e inserisce (PUSH) la mantissa nello stack; in questo modo, la
mantissa si viene a trovare nel nuovo ST(0), mentre la caratteristica passa
a ST(1).
Non è richiesto alcun operando esplicito; i registri ST(0) e ST(1) vengono
utilizzati come operandi impliciti.
ST(1) contiene la vera caratteristica (senza bias), mentre ST(0) contiene
la vera mantissa \(m\), salvata però in formato floating point nella forma
\(m \cdot 2^{16383}\), con il suo segno originale e con caratteristica data da 0
più il bias 16383 per i numeri in Extended Real; quindi, da ST(0)
possiamo prelevare la vera mantissa \(m\) trascurando la caratteristica, la quale
corrisponde a \(2^0 = 1\).
Come viene illustrato nel capitolo successivo, l'istruzione FXTRACT è molto
utile quando si vuole convertire un numero, da formato floating point a formato
ASCII per la visualizzazione sullo
schermo.
Se il flag ZE è mascherato e ST(0) contiene il valore zero, l'istruzione
FXTRACT produce la caratteristica \(\infty\) in ST(1) e la mantissa
0 (con il segno di SRC) in ST(0).
18.2.53 L'istruzione FYL2X - Compute y * log2x
Questa istruzione calcola \(y \cdot \log_2{x}\), con \(x\) che deve trovarsi in
ST(0) e \(y\) in ST(1); il risultato viene salvato in ST(1) e
successivamente viene effettuato un POP, che porta il risultato stesso in
ST(0).
Non è richiesto alcun operando esplicito; i registri ST(0) e ST(1)
vengono utilizzati come operandi impliciti.
Come sappiamo dalla matematica, nel campo numerico reale \(\log_2{x}\) è definito per
\(x \gt 0\), è negativo per \(0 \lt x \lt 1\), è nullo per \(x = 1\) ed è positivo
per \(x \gt 1\); inoltre:
\[ \lim_{x \to +\infty} \log_2{x} = +\infty \qquad \lim_{x \to 0^+} \log_2{x} = -\infty \]
Proprio per questo motivo, FYL2X richiede che il contenuto di ST(0) sia
\(x \gt 0\); il registro ST(1) può contenere un qualsiasi numero reale compreso
tra \(-\infty\) e \(+\infty\).
La Figura 18.13 illustra il grafico della funzione \(\log_2{x}\).
La tabella seguente illustra il risultato che si ottiene in base ai valori contenuti in
ST(0) e ST(1) nel caso in cui non si verifichi un underflow o un overflow;
F indica un numero in floating point.
(*) Eccezione di operazione aritmetica non valida (IE). Logaritmo di un
numero negativo o forme indeterminate; ad esempio,
\(({\pm 0}) \cdot \log_2{(+\infty)} = ({\pm 0}) \cdot ({+\infty})\).
(**) Eccezione di divisione per zero (ZE). Ad esempio,
\(({-F}) \cdot {\log_2{0}} = {\log_2{(0^{-F})}} = {\log_2{(1 / {0^F})}} = {\log_2{(1 / 0})}\).
Per lo zero viene ignorato il segno, per cui \({-0.0} = {+0.0}\).
Se il flag ZE è mascherato, il caso ** produce come risultato \(\infty\);
il segno è l'opposto di quello dell'operando in ST(1).
Se vogliamo calcolare logaritmi con una diversa base \(b \gt 0\), dobbiamo ricordare che:
\[ \log_b{x} = t \implies b^t = x \implies \log_2{(b^t)} = \log_2{x}
\implies t \cdot \log_2{b} = \log_2{x} \]
Ma \(t = \log_b{x}\), per cui:
\[ t \cdot \log_2{b} = \log_2{x} \implies \log_b{x} \cdot \log_2{b} = \log_2{x}
\implies \log_b{x} = {{\log_2{x}} \over {\log_2{b}}} \]
In sostanza, per calcolare \(\log_b{x}\) non dobbiamo fare altro che mettere \(x\) in
ST(0) e la costante \(y = 1 / \log_2{b}\) in ST(1) e poi chiamare
FYL2X; tale istruzione utilizza un algoritmo ottimizzato per eseguire il prodotto
\({ST(1)} \cdot \log_2{ST(0)}\).
18.2.54 L'istruzione FYL2XP1 - Compute y * log2(x + 1)
Questa istruzione calcola \(y \cdot \log_2{(x + 1.0)}\), con \(x\) che deve trovarsi
in ST(0) e \(y\) in ST(1); il risultato viene salvato in ST(1) e
successivamente viene effettuato un POP, che porta il risultato stesso in
ST(0).
Non è richiesto alcun operando esplicito; i registri ST(0) e ST(1) vengono
utilizzati come operandi impliciti.
Affinché FYL2XP1 produca un risultato valido, si deve avere
\(-(1 - \sqrt{2} / 2) \le x \le +(1 - \sqrt{2} / 2)\); la \(y\) invece può assumere un
qualsiasi valore reale compreso tra \(-\infty\) e \(+\infty\). Se \(x\), pur essendo un
Extended Real valido, non rientra nei limiti specificati, si ottiene un risultato
indefinito; in tal caso, la Intel raccomanda di non fare affidamento su eventuali
eccezioni generate dalla FPU in quanto questo aspetto non è stato definito nello
standard IEEE 754.
Nel campo numerico reale \(\log_2{(x + 1)}\) è definito per \(x \gt -1\), è negativo per
\(-1 \lt x \lt 0\), è nullo per \(x = 0\) ed è positivo per \(x \gt 0\); inoltre:
\[ \lim_{x \to +\infty} \log_2{(x + 1)} = +\infty \qquad
\lim_{x \to -1^+} \log_2{(x + 1)} = -\infty \]
La Figura 18.14 illustra tutti questi aspetti.
La tabella seguente illustra il risultato che si ottiene in base ai valori contenuti in
ST(0) e ST(1) nel caso in cui non si verifichi un underflow; F
indica un numero in floating point.
(*) Eccezione di operazione aritmetica non valida (IE). Logaritmo di un
numero negativo o forme indeterminate; ad esempio,
\(\infty \cdot \log_2{(0 + 1)} = \infty \cdot \log_2{1} = \infty \cdot 0\).
Bibliografia
Intel® 64 and IA-32 Architectures Software Developer’s Manual - Volume 1 -
Basic Architecture
(disponibile nella sezione
Documentazione tecnica di supporto al corso assembly dell’
Area Downloads di questo sito - 253665-sdm-vol-1.pdf)
Intel® 64 and IA-32 Architectures Software Developer’s Manual - Volume 2 -
Instruction Set Reference
(disponibile nella sezione
Documentazione tecnica di supporto al corso assembly dell’
Area Downloads di questo sito - 325383-sdm-vol-2abcd.pdf)
x87 Instructions FSIN, FCOS, FSINCOS, FPTAN and Math Functions sin, cos,
sincos, tan
(disponibile nella sezione
Documentazione tecnica di supporto al corso assembly dell’
Area Downloads di questo sito - x87trigfunc.pdf)