Assembly Avanzato con MASM
Capitolo 16: Rappresentazione dei numeri reali con la CPU
Secondo i matematici della scuola pitagorica, l'essenza della natura era
completamente rappresentata dai numeri interi positivi e dallo zero; in base a
quella filosofia di pensiero, qualsiasi grandezza (lunghezze, aree, volumi, etc)
era esprimibile sotto forma di un numero intero o, al più, di un rapporto tra
due numeri interi (quello che in matematica si definisce insieme dei numeri
razionali).
I pitagorici avevano scoperto che un qualsiasi numero razionale, espresso in base
\(10\), presentava la caratteristica di essere periodico; nella sua parte
decimale cioè, era sempre presente una sequenza finita di cifre che si ripeteva
all'infinito. Il numero \(1\), ad esempio, ha come parte decimale lo zero ripetuto
all'infinito, per cui può essere scritto simbolicamente come \(1,\overline{0}\);
analogamente, si ha \(1/3=0,\overline{3}\) o anche \(3/4=0,75\overline{0}\).
Si narra che Ippaso di Metaponto, anch'egli esponente della scuola pitagorica,
ebbe l'idea di disegnare sulla sabbia un quadrato di lato \(l = 1\), per poi tentare
di calcolarne la diagonale \(d\), nella speranza di ottenere un numero razionale come
risultato. Si tratta in pratica di applicare il Teorema di Pitagora per
determinare l'ipotenusa di un triangolo rettangolo isoscele i cui due cateti hanno
entrambi lunghezza \(l = 1\); se abbiamo a disposizione una calcolatrice ad elevata
precisione, otteniamo un risultato (approssimato) che ci fa venire dei seri dubbi
sulla sua razionalità:
\[
d^2 = l^2 + l^2 = 1^2 + 1^2 = 2 \implies d = \sqrt{2} = 1,4142135623730950488016887 ...
\]
La moderna matematica ci permette di dimostrare facilmente che \(\sqrt{2}\), in
effetti, non è un numero razionale!
Partiamo dal fatto che un numero razionale è un rapporto tra due numeri interi
\(a\) e \(b\), con \(b\) diverso da zero; inoltre, se \(a\) e \(b\) sono entrambi
pari, possiamo dividerli ripetutamente per \(2\) finché uno di essi non diventa
dispari (ad esempio, \(6 / 4 = 3 / 2\)). Assumendo che \(\sqrt{2}\) sia un numero
razionale, possiamo scrivere quindi:
\[
{a \over b} = \sqrt{2} \implies {a^2 \over b^2} = 2 \implies a^2 = 2b^2
\]
Ma se \(a^2\) è il doppio di \(b^2\), allora \(a^2\) è un numero pari; inoltre,
siccome il quadrato di un numero pari è pari e il quadrato di un numero dispari
è dispari, anche \(a\) è un numero pari. Per il ragionamento fatto in precedenza,
\(b\) deve essere allora un numero dispari.
Se \(a\) è pari, possiamo trovare un numero intero \(c\), non nullo, tale che
\(a = 2c\); sostituendo nel risultato precedente si ottiene quindi:
\[
a^2 = 2b^2 \implies ({2c})^2 = 2b^2 \implies 4c^2 = 2b^2 \implies 2c^2 = b^2
\]
Risulta cioè che \(b^2\) è il doppio di \(c^2\) e quindi è un numero pari, così
come lo deve essere allora anche \(b\); ma tutto ciò è impossibile visto che \(b\)
non può essere allo stesso tempo pari e dispari. Con un classico ragionamento per
assurdo abbiamo quindi dimostrato che \(\sqrt{2}\) non può essere espresso come
rapporto tra due numeri interi!
Anche Ippaso, attraverso calcoli ben più complicati, si accorse con grande
sconcerto che la parte frazionaria di \(d\) era formata da una sequenza interminabile
di cifre che non sembrava presentare alcuna periodicità; rivelò allora questa scoperta
agli altri pitagorici, ma si sentì rispondere di tenere la bocca chiusa per evitare
che questa "scandalosa" verità venisse a galla!
In pratica, Ippaso si era imbattuto nei cosiddetti numeri irrazionali;
si tratta di numeri (come \(\sqrt{2}, \pi, \exp\)) che non sono interi e non
possono essere espressi neppure sotto forma di rapporto tra numeri interi.
Secondo la leggenda, Ippaso non mantenne il segreto e per questo venne
affogato in mare!
16.1 Principali insiemi numerici della matematica
In matematica, la totalità dei numeri interi positivi forma l'insieme dei
numeri naturali, indicato con il simbolo \(\Bbb N\); questo nome deriva dal
fatto che tali numeri vengono appresi in modo naturale, sin da bambini, quando si
impara a contare.
Nell'insieme \(\Bbb N\) solo l'addizione e la moltiplicazione sono operazioni "ben
definite" (o interne); infatti, addizionando o moltiplicando due numeri
interi positivi, si ottiene come risultato un numero intero positivo. Se proviamo
ad effettuare una sottrazione tra due numeri interi positivi, vediamo però che tale
operazione è possibile solo se il minuendo è strettamente maggiore del
sottraendo; aggiungendo lo zero all'insieme \(\Bbb N\), diventa possibile
anche la sottrazione tra due numeri interi positivi uguali (\(n-n=0\)).
Si rende necessario quindi estendere l'insieme \(\Bbb N\) in modo che sia possibile
la sottrazione tra due numeri interi positivi qualunque; ciò viene ottenuto
definendo l'insieme dei numeri interi relativi, indicato con il simbolo
\(\Bbb Z\) e costituito dalla totalità dei numeri interi positivi, negativi e dallo
zero. Fissata una retta, un suo punto di riferimento (che rappresenta lo \(0\)) e
una distanza unitaria \(U\), tutti gli infiniti punti alla destra dello \(0\), posti
ad intervalli di \(U\), rappresentano nell'ordine i numeri positivi \(+1, +2, +3\),
etc; analogamente, tutti gli infiniti punti alla sinistra dello \(0\), posti ad
intervalli di \(U\), rappresentano nell'ordine i numeri negativi \(-1, -2, -3\),
etc. La Figura 16.1 illustra questa situazione.
Calcolare, ad esempio, \(2-5\), significa partire dallo \(0\), andare verso
destra di \(2\) punti e poi andare verso sinistra di \(5\) punti; alla fine ci
ritroviamo al punto indicato con \(-3\), per cui \(2-5=-3\).
Risulta evidente che l'insieme \(\Bbb N\) è interamente contenuto nell'insieme
\(\Bbb Z\); in simboli matematici, \(\Bbb N \subset \Bbb Z\).
Nell'insieme \(\Bbb Z\) l'addizione, la sottrazione e la moltiplicazione sono
operazioni ben definite; si presenta però il problema della divisione che risulta
possibile solo quando il numeratore è un multiplo intero del
denominatore. Si rende necessario quindi estendere l'insieme \(\Bbb Z\) in
modo che sia possibile la divisione tra due numeri interi relativi qualunque; ciò
viene ottenuto definendo l'insieme dei numeri razionali, indicato con il
simbolo \(\Bbb Q\) e costituito dalla totalità dei numeri frazionari distinti che
si ottengono dividendo tra loro due numeri interi relativi \(a\) e \(b\), con
\(b\) diverso da zero.
Risulta evidente che l'insieme \(\Bbb Z\) è interamente contenuto nell'insieme
\(\Bbb Q\), dato che ogni intero relativo \(n\) può essere scritto come \(n/1\);
in simboli matematici, \(\Bbb Z \subset \Bbb Q\).
Come scoperto però da Ippaso da Metaponto, l'insieme \(\Bbb Q\) non risolve
tutti i problemi in quanto esistono numeri che non sono interi e non possono essere
espressi neppure come rapporto tra due numeri interi; tali numeri formano
l'insieme dei numeri irrazionali, indicato con il simbolo \(\Bbb I\).
Unendo l'insieme dei numeri razionali con quello dei numeri irrazionali, si
ottiene l'insieme dei numeri reali, indicato con il simbolo \(\Bbb R\); in
simboli matematici, \(\Bbb R = \Bbb Q \cup \Bbb I\).
Tornando alla rappresentazione di \(\Bbb Z\) sulla retta (Figura 16.1), si può
notare che tra due numeri relativi consecutivi \(m\) e \(m+1\), esistono infiniti
punti della retta stessa; non è possibile quindi stabilire una corrispondenza
biunivoca tra i numeri relativi e i punti di una retta. Lo stesso discorso vale
anche per l'insieme \(\Bbb Q\); in matematica si dimostra che i punti della retta
non associabili ad alcun numero razionale rappresentano proprio i numeri
irrazionali e l'insieme di tutti i numeri, razionali e irrazionali, copre quindi
l'intera retta.
Si può stabilire così una corrispondenza biunivoca tra i numeri reali e i punti
di una retta; non esistono punti di una retta non rappresentabili con un numero
reale, per cui, ad ogni punto di una retta corrisponde un numero reale e viceversa.
Ciò è possibile perché, tra due numeri reali qualunque, per quanto vicini possano
essere, ne esistono infiniti altri (proprio come accade per i punti di una retta);
si dice allora che \(\Bbb R\) è un insieme non numerabile, nel senso che
non è possibile mettere in qualsiasi ordine i numeri reali come si fa, ad esempio,
con i numeri interi positivi (\(1, 2, 3\), etc).
Una ulteriore estensione di \(\Bbb R\) si rende necessaria affinché siano
possibili operazioni come, ad esempio, il logaritmo di un numero negativo; ciò si
ottiene introducendo l'insieme dei numeri complessi, indicato con il simbolo
\(\Bbb C\) e costituito da tutte le possibili coppie ordinate \((a, b)\) di numeri
reali.
Definendo l'unità immaginaria \(i\), tale che \(i^2 = -1\) e quindi
\(i = \sqrt{-1}\), un numero complesso \(z\) può essere scritto in forma
algebrica come \(z = a + ib\); si dice che \(a\) è la parte reale di
\(z\) e \(b\) è il coefficiente dell'immaginario (in simboli, \(a=Re(z),
b=Im(z)\)).
Si vede subito che \(\Bbb R\) è totalmente contenuto in \(\Bbb C\) (in simboli
\(\Bbb R \subset \Bbb C\)); infatti, posto \(b = 0\), si ottengono tutti i numeri
reali in forma di coppie ordinate \((a,0)\).
16.2 Codifica dei numeri interi con la CPU
Nel tutorial Assembly Base abbiamo visto che la CPU lavora con due
livelli di tensione elettrica (basso, alto) che possiamo associare
ai due simboli \(0\) e \(1\); si tratta degli unici due simboli che formano
il sistema binario o sistema di numerazione posizionale in base
\(2\).
Su una architettura a \(n\) bit, possiamo codificare un sottoinsieme finito di
\(\Bbb N\) più lo zero, costituito da tutti i numeri interi positivi compresi tra
\(0\) e \(2^n-1\); a tale proposito, ci serviamo della sequenza consecutiva e
contigua di numeri binari rappresentabili con \(n\) bit, a partire da \(0\). Nel
caso, ad esempio, di una architettura a \(8\) bit, abbiamo una sequenza di numeri
binari che va da \(00000000b\) a \(11111111b\); tale sequenza viene utilizzata
per codificare tutti i numeri interi positivi da \(0\) a \(2^8-1=255\).
La CPU non ha la più pallida idea di cosa sia un numero intero positivo, ma
abbiamo visto che attraverso apposite reti logiche, le codifiche binarie di
quei numeri stessi possono essere usate come operandi a \(n\) bit sui quali
eseguire, via hardware, operazioni elementari come addizioni, sottrazioni,
moltiplicazioni e divisioni; inoltre, abbiamo anche visto che quelle stesse
operazioni possono essere eseguite via software su operandi di ampiezza qualunque
attraverso appositi algoritmi che scompongono tali numeri in gruppi di \(n\) bit.
Grazie poi ad un effetto collaterale dell'aritmetica modulare, sappiamo
che la CPU è in grado di codificare via hardware anche un sottoinsieme
finito di \(\Bbb Z\); in questo caso abbiamo visto che, su una architettura a
\(n\) bit, possiamo codificare tutti i numeri interi con segno compresi tra
\(-(2^n/2)\) e \(+((2^n/2)-1)\). A tale proposito, ci serviamo della sequenza
consecutiva e contigua di numeri binari rappresentabili con \(n\) bit, a partire
da \(1000000...0b\) (in questo modo, i numeri negativi hanno il bit più
significativo che vale \(1\), mentre i numeri positivi hanno il bit più
significativo che vale \(0\)); nel caso, ad esempio, di una architettura a
\(8\) bit, abbiamo una sequenza di numeri binari che va da \(10000000b\) a
\(01111111b\) e tale sequenza viene utilizzata per codificare tutti i numeri
interi con segno da \(-(2^8/2)=-128\) a \(+((2^8/2)-1)=+127\).
Valgono tutte le considerazioni esposte in precedenza sulle operazioni eseguibili
via hardware sui numeri interi con segno a \(n\) bit e via software sui numeri
interi con segno di ampiezza qualunque.
16.3 Codifica dei numeri reali con la CPU
Come abbiamo appena visto, la CPU è in grado di operare via hardware su un
sottoinsieme finito di numeri interi, con o senza segno; ciò permette di eseguire
su tali numeri, operazioni elementari ad altissima velocità.
Ben diverso è il discorso quando si intende operare sui numeri reali; in tal caso,
infatti, in assenza di alternative si è costretti a scrivere complessi algoritmi
che comportano una elaborazione relativamente lenta da parte della CPU.
Proprio per alleviare questo problema, sin dai tempi delle vecchie CPU 8086,
la Intel ha introdotto un microprocessore supplementare denominato
coprocessore matematico o FPU (Floating Point Unit); la
FPU verrà trattata nel capitolo successivo, mentre in questo capitolo ci
occuperemo degli aspetti teorici che stanno alla base della rappresentazione dei
numeri reali con una normale CPU.
Un qualunque numero reale \(x\) può essere scritto nella forma:
\[ x = m b^e \]
detta forma normalizzata in base \(b\) di \(x\); per "forma normalizzata"
si intende il fatto che \(x\) risulta essere del tipo \(\pm(0,...) \cdot b^e\),
con la prima cifra dopo la virgola diversa da zero (se il numero non è nullo).
In questa formula, \(b\) è la base del sistema di numerazione usato, mentre
\(e\) è la caratteristica e rappresenta un numero intero con segno; \(m\) è
la mantissa e per quanto appena visto deve essere tale che:
\[ {1 \over b} \le \vert m \vert \lt 1 \]
Indicando con \(c_1, c_2, c_3, ...\) le cifre di \(m\), si ha:
\[ m = c_1 b^{-1} + c_2 b^{-2} + c_3 b^{-3} + ... \]
Ad esempio, se \(x = 245,65134\), abbiamo in base \(b=10\):
\[
m = 2 \cdot 10^{-1} + 4 \cdot 10^{-2} + 5 \cdot 10^{-3} + 6 \cdot 10^{-4} +
5 \cdot 10^{-5} + 1 \cdot 10^{-6} + 3 \cdot 10^{-7} + 4 \cdot 10^{-8} =
0,24565134
\]
Siccome la parte intera di \(x\) è \(245\), dobbiamo moltiplicare \(m\) per
\(1000 = 10^3\), per cui \(e=3\); si ha quindi:
\[ x = m \cdot b^e = 0,24565134 \cdot 10^3 = 245,65134 \]
Viceversa, sapendo che \(b=10\), \(m=-0,153428\), \(e=-2\), ricaviamo facilmente:
\[ x = m \cdot b^e = -0,153428 \cdot 10^{-2} = -0,00153428 \]
Per eseguire addizioni o sottrazioni tra due numeri reali in forma normalizzata,
bisogna prima riportare i due numeri alla stessa caratteristica; ciò equivale,
infatti, ad incolonnare le parti intere e le parti frazionarie dei due numeri. La
regola prevede che venga presa la caratteristica maggiore tra le due; una volta
compiuto questo passo, si tratta di eseguire l'operazione (addizione o sottrazione)
sulle mantisse, per cui ci riconduciamo a addizioni e sottrazioni tra numeri interi
che già abbiamo studiato nel tutorial Assembly Base.
Dati quindi \(x_1=m_1 \cdot b^{e_1}\) e \(x_2=m_2 \cdot b^{e_2}\), con \(e_1=e_2=e\),
si ha:
\[
x_1 \pm x_2 = (m_1 \cdot b^e) \pm (m_2 \cdot b^e) = (m_1 \pm m_2) \cdot b^e
\]
Supponiamo, ad esempio, di voler sommare i due numeri reali seguenti, espressi in
base \(b=10\):
\[
x_1=m_1 \cdot b^{e_1} = -0,15478329 \cdot 10^3 = -154,78329 \\
x_2=m_2 \cdot b^{e_2} = +0,61873214 \cdot 10^5 = +61873,214
\]
Portando \(x_1\) a caratteristica \(5\) otteniamo \(x_1=-0,0015478329 \cdot 10^5\);
a questo punto sommiamo le due mantisse ottenendo:
\begin{align}
& -0,0015478329 + \cr
& +0,6187321400 = \cr
\hline
& +0,6171843071
\end{align}
Il risultato finale è \(+0,6171843071 \cdot 10^5\) che è già in forma normalizzata
per cui non necessita di alcuna modifica della caratteristica.
La moltiplicazione tra due numeri reali in forma normalizzata è più semplice di
quanto si possa pensare; infatti, dati \(x_1=m_1 \cdot b^{e_1}\) e \(x_2=m_2
\cdot b^{e_2}\), si ha:
\[
x_1 \cdot x_2 = (m_1 \cdot b^{e_1}) \cdot (m_2 \cdot b^{e_2}) =
(m_1 \cdot m_2) \cdot (b^{e_1} \cdot b^{e_2}) =
(m_1 \cdot m_2) \cdot b^{e_1 + e2}
\]
La caratteristica del risultato quindi è la somma delle due caratteristiche,
mentre la mantissa è il prodotto delle due mantisse; in sostanza, ci riconduciamo
a somme e prodotti tra numeri interi che già abbiamo studiato nel tutorial
Assembly Base.
Ad esempio, se \(x_1 = 0,31479614 \cdot 10^{-2}\) e \(x_2 = 0,94173212 \cdot
10^5\), in base \(b=10\), si ha:
\[
x_1 \cdot x_2 = (0,31479614 \cdot 0,94173212) \cdot 10^{(-2 + 5)} =
0,29645363629 \cdot 10^3
\]
Il risultato finale è già in forma normalizzata per cui non necessita di alcuna
modifica della caratteristica.
Analoghe considerazioni per la divisione tra due numeri reali in forma normalizzata
(con il secondo numero diverso da zero); dati \(x_1=m_1 \cdot b^{e_1}\) e
\(x_2=m_2 \cdot b^{e_2}\), si ha:
\[
{x_1 \over x_2} = {{m_1 \cdot b^{e_1}} \over {m_2 \cdot b^{e_2}}} =
{m_1 \over m_2} \cdot {b^{e_1} \over b^{e_2}} =
{m_1 \over m_2} \cdot b^{e_1 - e2}
\]
La caratteristica del risultato quindi è la differenza tra le due caratteristiche,
mentre la mantissa è il quoziente tra le due mantisse; in sostanza, ci riconduciamo
a sottrazioni e divisioni tra numeri interi che già abbiamo studiato nel tutorial
Assembly Base.
Ad esempio, se \(x_1 = -0,61895473 \cdot 10^2\) e \(x_2 = +0,18567488 \cdot
10^{-3}\), in base \(b=10\), si ha:
\[
{x_1 \over x_2} = {-0,61895473 \over +0,18567488} \cdot 10^{(2-(-3))} =
-3,33354048754 \cdot 10^5
\]
Il risultato finale non è in forma normalizzata per cui è necessario spostare di
un posto verso destra le cifre della mantissa, aumentando quindi di \(1\) la
caratteristica; otteniamo così il numero reale \(-0,333354048754 \cdot 10^6 \).
16.3.1 Codifica binaria dei numeri reali
Le considerazioni appena esposte hanno un valore puramente teorico; infatti, non
avendo posto dei limiti all'ampiezza della caratteristica e della mantissa, risulta
possibile rappresentare in forma normalizzata qualsiasi numero reale, con precisione
pressoché infinita.
Il discorso cambia nel momento in cui vogliamo codificare in binario i numeri reali
in forma normalizzata; in tal caso, infatti, dobbiamo tenere conto dei limiti fisici
dei registri o delle locazioni di memoria che utilizziamo per contenere la mantissa
e la caratteristica.
La cosa più ovvia da fare consiste allora nello stabilire una serie di regole; in
sostanza, dobbiamo decidere quanti bit riservare alla codifica dei numeri reali in
forma normalizzata e come ripartire tali bit tra mantissa e caratteristica.
Possiamo decidere, ad esempio, di utilizzare una codifica a \(32\) bit riservando
i \(24\) bit da \(0\) a \(23\) alla mantissa, i \(7\) bit da \(24\) a \(30\) alla
caratteristica e il bit \(31\) al segno della mantissa secondo la solita convenzione
(\(0 =\) segno positivo, \(1 =\) segno negativo). Si perviene allora alla situazione
illustrata in Figura 16.2.
Da quanto abbiamo visto in precedenza, la mantissa, composta dalle cifre binarie
\(c_0, c_1, c_2, ..., c_{23}\) (a partire dalla meno significativa), deve essere
interpretata come:
\[ m = c_{23} 2^{-1} + c_{22} 2^{-2} + c_{21} 2^{-3} + ... + c_{0} 2^{-24} \]
La caratteristica è un numero intero con segno in complemento a \(2\), modulo
\(2^7=128\) e rappresenta l'esponente \(e\) a cui bisogna elevare la base \(2\);
quindi, la codifica di Figura 16.2 rappresenta il numero reale:
\[
x = (-1)^S \cdot (c_{23} 2^{-1} + c_{22} 2^{-2} + c_{21} 2^{-3} + ... +
c_{0} 2^{-24}) \cdot 2^e
\]
Supponiamo di voler codificare il numero reale \(x = 3,625\). La parte intera è
\(3\), che in binario si scrive \(11_2\), mentre la parte frazionaria è \(0,625\),
che in binario si scrive \(0,101_2\); infatti:
\[
0,101_2 = 1 \cdot 2^{-1} + 0 \cdot 2^{-2} + 1 \cdot 2^{-3} =
1 \cdot 0,5 + 0 \cdot 0,25 + 1 \cdot 0,125 = 0,5 + 0 + 0,125 = 0,625
\]
Si ha quindi \(x=11,101_2\), che normalizzato diventa \(0,11101_2\); estendendo
tale numero binario a \(24\) bit si ottiene la mantissa
\(m=11101000000000000000000\)\(0_2\) (ovviamente, trattandosi di un numero
decimale minore di \(1\), lo possiamo estendere a \(24\) bit aggiungendo zeri
non significativi alla sua destra). Il segno della mantissa è positivo, per cui
\(S=0\).
La caratteristica è \(10_2=2\) in quanto dobbiamo far scorrere le cifre della
mantissa di due posti verso sinistra; in definitiva, abbiamo
\(x=0,11101_2 \cdot 2^{10_2}\), che codificato in binario diventa:
\[ x = 0 \vert 0000010 \vert 111010000000000000000000 \]
Viceversa, supponiamo di avere la codifica binaria:
\[ x = 1 \vert 1111110 \vert 111001110100000000000000 \]
La mantissa (eliminando gli zeri non significativi a destra) è \(1110011101_2\)
e il suo segno è negativo (\(S=1\)); si ha quindi:
\begin{align}
m &= -0,1110011101_2 \\
&= -(1 \cdot 2^{-1} + 1 \cdot 2^{-2} + 1 \cdot 2^{-3} +
0 \cdot 2^{-4} + 0 \cdot 2^{-5} + 1 \cdot 2^{-6} + 1 \cdot 2^{-7} +
1 \cdot 2^{-8} + 0 \cdot 2^{-9} + 1 \cdot 2^{-10}) \\
&= -0,9033203125
\end{align}
La caratteristica, in complemento a \(2\) modulo \(128\), codifica il numero
negativo \(-2\), per cui si ha, in definitiva:
\[ x = -0,9033203125 \cdot 2^{-2} = -0,9033203125 / 4 = -0,225830078125 \]
Si può notare che, nella rappresentazione dei numeri reali in forma normalizzata,
la posizione della virgola varia al variare della caratteristica; si parla allora
di numeri in virgola mobile o floating point numbers.
16.3.2 Caratteristiche dei numeri in virgola mobile a n bit
Nel caso generale, consideriamo una codifica a \(n\) bit, con un bit destinato
al segno, \(k\) bit destinati alla mantissa e \(h\) bit destinati alla
caratteristica; si ha quindi:
\[ n = k + h + 1 \]
Innanzi tutto, si vede subito che non è possibile codificare alcun numero
irrazionale in forma normalizzata; infatti, avendo a disposizione solo \(k\)
bit per la mantissa, risulta finito e limitato il numero di cifre dopo la
virgola. Sarà possibile codificare esclusivamente un sottoinsieme finito e
limitato dei numeri razionali; analizziamo quindi le caratteristiche di tale
sottoinsieme.
La mantissa, per come è stata definita, deve essere tale che:
\[
1 \cdot 2^{-1} \le \vert m \vert \le
1 \cdot 2^{-1} + 1 \cdot 2^{-2} + 1 \cdot 2^{-3} + ... + 1 \cdot 2^{-k}
\]
La caratteristica deve essere tale che:
\[ -(2^h / 2) \le e \le +((2^h / 2) - 1) \]
In pratica, come è stato appena spiegato, con questo sistema possiamo
codificare un sottoinsieme finito e limitato dei numeri razionali; inoltre,
all'interno di tale sottoinsieme, determinati numeri razionali non risultano
codificabili. Osserviamo, infatti, che mentre per la parte intera non c'è
alcun problema, la parte frazionaria risulterà invece codificabile solo se essa
è esprimibile sotto forma di somma di potenze intere di \(2\); ad esempio, in
precedenza abbiamo visto che \(x = 3,625\) è codificabile in quanto la parte
frazionaria \(0,625\) è esprimibile come \(0,101_2\).
Tutto ciò equivale ad affermare che, dato un numero razionale \(x\), espresso
come rapporto tra due numeri interi relativi \(a\) e \(b\), con \(b \neq 0\),
tale numero risulterà codificabile in binario se e solo se il suo denominatore
è una potenza intera di \(2\); ad esempio, \(1 / 3\) non è codificabile in
binario in quanto \(3\) non è una potenza intera di \(2\).
Siccome il nostro sottoinsieme di numeri razionali codificabili in binario è
finito, esso risulterà dotato di massimo e di minimo; visto e considerato che
tale sottoinsieme è simmetrico rispetto allo zero, possiamo limitarci ad
esaminare solo i numeri positivi.
Il minimo positivo è:
\[ x_{min} = 2^{-1} \cdot 2^{-2^{h-1}} \]
Il massimo positivo è:
\[ x_{max} = (1 - 2^{-k}) \cdot (2^{(2^{h - 1} - 1)}) \]
Ad esempio, per la codifica a \(32\) bit di Figura 16.2, il minimo positivo è:
\[
x_{min} = 0 \vert 1000000 \vert 100000000000000000000000 =
2^{-1} \cdot 2^{-64} = 2^{-65}
\]
Il massimo positivo è:
\[
x_{max} = 0 \vert 0111111 \vert 111111111111111111111111 =
(1 - 2^{-24}) \cdot (2^{63}) = 2^{63} - 2^{39}
\]
Analizzando l'insieme dei numeri in virgola mobile così ottenuto, si può notare
che, più ci si avvicina a \(x_{min}\) e più i numeri si addensano; analogamente,
più ci si avvicina a \(x_{max}\) e più i numeri si diradano. In sostanza, i
numeri in virgola mobile risultano distribuiti in modo non uniforme sull'asse
reale.
Una ulteriore conseguenza delle considerazioni appena esposte è che per i
numeri in virgola mobile non sono più valide le proprietà tipiche dei numeri
reali; non risultano applicabili quindi le seguenti proprietà:
\begin{align}
& a + b = b + a \qquad a \cdot b = b \cdot a \qquad (commutativa) \cr
& a + (b + c) = (a + b) + c \qquad a \cdot (b \cdot c) = (a \cdot b) \cdot c
\qquad (associativa) \cr
& a \cdot (b + c) = (a \cdot b) + (a \cdot c) \qquad (distributiva)
\end{align}
Può anche capitare che, effettuando addizioni, sottrazioni, moltiplicazioni e
divisioni tra numeri in virgola mobile, si ottenga un risultato che non
appartiene all'insieme dei numeri in virgola mobile.
Per ovviare a tutti questi inconvenienti, si ricorre a procedure che hanno lo
scopo di effettuare appositi arrotondamenti; in questo modo, i vari calcoli
producono sempre un risultato che ricade nell'insieme dei numeri in virgola
mobile.
16.3.3 Numeri in virgola fissa
Esiste anche un sistema di codifica dei numeri reali che prevede una
posizione fissa per la virgola; si parla allora di numeri in virgola
fissa o fixed point numbers. In sostanza, data una codifica a
\(n\) bit, si stabilisce a priori in quale bit debba trovarsi la virgola;
a questo punto, tutti i bit alla sinistra della virgola contengono la
parte intera, mentre tutti i bit alla sua destra contengono la parte
frazionaria del numero da rappresentare. Chiaramente, si tratta di una
rappresentazione dei numeri reali in forma non normalizzata.
Il vantaggio di questo sistema è dato dalla notevole semplificazione nella
esecuzione di addizioni e sottrazioni in quanto i numeri risultano già
incolonnati correttamente; lo svantaggio evidente è che, con \(n\) bit, si
ottiene un insieme di numeri in virgola fissa enormemente meno esteso di
quello dei numeri in virgola mobile.
Consideriamo, ad esempio, una codifica a \(32\) bit, con i primi \(16\) bit
che contengono la parte frazionaria, i successivi \(15\) bit che contengono
la parte intera e il bit più significativo che codifica il segno; in questo
caso, il minimo positivo è:
\[ x_{min} = 000000000000000,000000000000000{1}_2 = 2^{-16} \]
mentre il massimo positivo è:
\[ x_{max} = 111111111111111,111111111111111{1}_2 \lt 2^{15} = 32768 \]
16.4 Formati standard IEEE per i numeri in virgola mobile
Se proviamo a scrivere un programma che definisce una variabile del tipo:
float_var32 dd 3.625
e poi la visualizza sullo schermo attraverso la procedura writeBin32,
otteniamo il seguente output:
01000000011010000000000000000000B
Come si può notare, tale output è completamente diverso da quello che avevamo
ottenuto in un precedente esempio relativo proprio al numero \(x = 3,625\).
Questa differenza si spiega con il fatto che, tutti i compilatori, assemblatori
e interpreti, utilizzano un metodo standard di codifica dei numeri in virgola
mobile, definito dalla IEEE (Institute of Electrical and Electronics
Engineers); tale metodo è molto più sofisticato di quello presentato in
precedenza negli esempi che avevano uno scopo puramente didattico.
Lo standard IEEE 754 o IEEE Standard for Floating-Point Arithmetic
(e successive revisioni) definisce il formato utilizzato per la rappresentazione
dei numeri in virgola mobile sui computer; inoltre, si occupa della codifica di
casi particolari come l'infinito positivo e negativo, il risultato di una
operazione non valida (come una divisione per zero o il logaritmo di un numero
negativo). Lo standard definisce anche le operazioni matematiche eseguibili sui
numeri in floating point, i metodi di arrotondamento e la gestione delle eccezioni.
Sono previsti tre formati binari principali per i numeri in virgola mobile:
- single precision a 32 bit
- double precision a 64 bit
- quadruple precision a 128 bit
Restano pienamente validi i concetti di bit di segno, mantissa e caratteristica
che abbiamo analizzato in precedenza; vedremo però che determinati valori di
tali campi vengono utilizzati per codificare casi particolari.
Consideriamo quindi una codifica binaria a \(n\) bit, con \(h\) bit per la
caratteristica, \(k\) bit per la mantissa e un bit riservato al segno della
mantissa; si ha quindi \(n = h + k + 1\).
Il bit più significativo, indicato con \(S\), codifica il segno della mantissa;
come già sappiamo, il valore \(0\) indica segno positivo, mentre il valore \(1\)
indica segno negativo.
Gli \(h\) bit che precedono \(S\) codificano la caratteristica, indicata con
\(e\); per quanto visto negli esempi precedenti, dovrebbe trattarsi di un numero
con segno in complemento a \(2\), modulo \(2^h\), che rappresenta l'esponente
della base \(2\). Il problema che si presenta è che, con questo tipo di codifica
della caratteristica, si creano notevoli complicazioni quando si devono eseguire
confronti tra numeri in virgola mobile; come sappiamo, tutto ciò si traduce in
una maggiore complessità dei circuiti logici che svolgono tali confronti.
Per ovviare a questo inconveniente, si ricorre ad un metodo di codifica della
caratteristica detto eccesso p, con il numero p che rappresenta
il cosiddetto bias; innanzi tutto, il valore \(p\) viene calcolato come
segue:
\[ p = (2^h / 2) - 1 = 2^{h - 1} - 1 \]
Il valore \(p\) così ottenuto viene sommato alla vera caratteristica \(E\) in modo
da ottenere una caratteristica sempre positiva:
\[ e = E + p \]
I valori \(e = 0\) e \(e = 2^h - 1\) sono riservati per gli scopi che vengono
illustrati più avanti; restano a disposizione quindi \(2^h - 2\) valori di \(e\)
per la codifica della vera caratteristica \(E\).
Supponiamo di avere, ad esempio, \(h = 8\); in tal caso si ha \(p = 2^7 - 1 = 127\),
mentre i valori \(e = 0\) e \(e = 2^8 - 1 = 255\) sono riservati. Con \(2^8 = 256\)
valori binari, possiamo rappresentare tutti i numeri interi relativi \(E\) compresi
tra \(-128\) e \(+127\); quindi:
\[E = -128, -127, -126, -125, \dots, -3, -2, -1, 0, +1, +2, +3, \dots, +125, +126, +127\]
La caratteristica \(e\) corretta con il bias \(p = 127\) è allora:
\[e = E + p = -1, 0, +1, +2, \dots, +124, +125, +126, +127, +128, +129, +130, \dots, +252, +253, +254\]
Osserviamo ora che \(e = -1\) con \(8\) bit si scrive \(11111111_2\), che in base
\(10\) corrisponde a \(255\); come è stato appena spiegato, tale valore è riservato,
per cui non possiamo rappresentare \(E = -128\). Anche \(e = 0\) è riservato, per cui
non possiamo rappresentare \(E = -127\); in definitiva, i valori di \(E\) disponibili
vanno da \(-126\) a \(+127\). Sommando il bias \(127\) a \(E\) otteniamo le
corrispondenti codifiche di \(e\) che vanno da \(+1\) a \(+254\), con \(E = 0\)
codificato come \(+127\); la caratteristica \(e\) risulterà essere quindi sempre un
numero positivo!
Grazie a questo metodo, il confronto tra numeri in virgola mobile si riduce ad
un confronto tra numeri positivi. Si può notare che, se i due numeri in virgola
mobile da confrontare hanno segno \(S\) diverso, ovviamente il più grande è
quello con \(S = 0\) (mantissa positiva); se il segno è lo stesso, si passa al
confronto delle caratteristiche. Se le caratteristiche sono diverse, ovviamente
il numero più grande (in valore assoluto) è quello con la caratteristica maggiore;
se le due caratteristiche sono uguali, si passa infine al confronto tra le mantisse.
La mantissa \(m\), come al solito, rappresenta le cifre dopo la virgola; a
differenza però di quanto illustrato nei precedenti esempi, lo standard IEEE
754 prevede che \(m\) debba essere interpretata come \(\pm(1,...)\). Abbiamo
quindi un bit nascosto che implicitamente vale sempre \(1\); in totale, per la
codifica della mantissa risultano disponibili \(k + 1\) bit effettivi.
Per "forma normalizzata" in questo caso si intende il fatto che il primo bit
non nullo del numero da rappresentare deve trovarsi immediatamente alla sinistra
della virgola; ad esempio, normalizzare \(111,01001{1_2}\) significa convertirlo
in:
\[ 1,1101001{1_2} \cdot 2^2 \qquad (E = 2, m = 1101001{1_2}) \]
Analogamente, normalizzare \(-0,0010010{1_2}\) significa convertirlo in:
\[ -1,0010100{0_2} \cdot 2^{-3} \qquad (E = -3, m = -0010100{0_2}) \]
Nel momento in cui vogliamo decodificare un numero in virgola mobile codificato
secondo lo standard IEEE 754, dobbiamo prima ricavare caratteristica e
mantissa in questo modo:
\begin{align}
E &= e - p \cr
M &= 1,m
\end{align}
A questo punto, la decodifica del numero risulterà essere:
\[ x = (-1)^S \cdot 2^E \cdot M \]
Analizziamo ora i casi particolari, i quali vengono codificati assegnando
determinati valori alla caratteristica e alla mantissa; nel caso più generale
che abbiamo appena illustrato, si hanno i numeri in virgola mobile in forma
normalizzata, con caratteristica compresa tra \(1\) e \(2^h - 2\) e mantissa
maggiore o uguale a zero in valore assoluto.
Se la caratteristica e la mantissa sono entrambe nulle, si ha la codifica del
numero zero; il bit di segno può assumere i due valori \(0\) e \(1\), per cui
lo zero avrà le due possibili rappresentazioni \(+0\) e \(-0\).
Se la caratteristica è zero, mentre la mantissa è diversa da zero, si ha la
codifica dei cosiddetti numeri denormalizzati; si tratta di numeri
compresi, in valore assoluto, tra zero e il più piccolo numero normalizzato
rappresentabile (estremi esclusi). Abbiamo visto che la forma normalizzata
impone che la mantissa sia del tipo \(\pm(1,...)\); togliendo questo vincolo
è possibile usare la mantissa stessa per rappresentare numeri ancora più
piccoli di quelli normalizzati.
Per i numeri denormalizzati, convenzionalmente si pone \(E = -(2^{h - 1} - 2)\);
attenzione quindi al fatto che si tratta di una convenzione, per cui non
si deve calcolare \(E = e - p\) (ad esempio, per \(h = 8\) si deve porre
\(E = -(2^7 - 2) = -126\) e non \(E = e - p = 0 - 127 = -127\))!
La vera mantissa per i numeri denormalizzati è data da \(M = 0,m\); il bit
nascosto vale quindi \(0\) e non \(1\).
Consideriamo il caso \(h = 8\), \(k = 23\); il minimo positivo in forma
normalizzata è quindi:
\[
xn_{min} = 2^{-126} \cdot (1,0000000000000000000000{0_2}) = 2^{-126} \cdot 1
= 2^{-126}
\]
In forma denormalizzata si ha \(E = -(2^7 - 2) = -126\); il minimo positivo è
quindi:
\[
xd_{min} = 2^{-126} \cdot (0,0000000000000000000000{1_2}) =
2^{-126} \cdot 2^{-23} = 2^{-149}
\]
Il massimo positivo è:
\[
xd_{max} = 2^{-126} \cdot (0,1111111111111111111111{1_2}) =
2^{-126} \cdot (1 - 2^{-23}) = 2^{-126} - 2^{-149}
\]
che è inferiore al minimo positivo in forma normalizzata.
Se la caratteristica vale \(255\), mentre la mantissa è zero, si ha la codifica
dell'infinito; il bit di segno può valere \(0\) o \(1\) e ciò permette di
rappresentare, rispettivamente, \(+\infty\) e \(-\infty\).
Se la caratteristica vale \(255\), mentre la mantissa è diversa da zero, si ha
la codifica di un risultato frutto di una operazione non valida (ad esempio,
\((0 / 0)\), \((0 \cdot \infty)\), \((\sqrt{-1})\), etc); si utilizza in questo
caso la sigla NaN che sta per Not a Number.
Il bit di segno viene generalmente ignorato; il primo bit della mantissa,
invece, indica il tipo di NaN e vale \(1\) per il quiet NaN (o
qNaN) e \(0\) per il signaling NaN (o sNaN).
Un qNaN viene prodotto da una operazione non valida e si propaga tra le
operazioni successive senza che venga segnalato; è compito del software quindi
effettuare le necessarie verifiche.
Un sNaN viene prodotto da una operazione non valida ed è accompagnato
da una apposita segnalazione; nel capitolo successivo vedremo che la FPU
segnala un sNaN attraverso apposite eccezioni, le quali possono essere
intercettate e gestite dai programmi.
Analizziamo ora le caratteristiche dei principali formati binari per i numeri
in virgola mobile; questi e altri formati vengono illustrati più in dettaglio
nel capitolo successivo.
16.4.1 Formato single precision a 32 bit
Il formato single precision a 32 bit è il più importante in quanto
è l'unico considerato obbligatorio dallo standard IEEE 754; tutti gli
altri formati sono considerati opzionali.
La Figura 16.3 illustra la struttura del formato in precisione singola a 32
bit.
Il bit S in posizione 31 rappresenta il segno della mantissa. Gli
8 bit che precedono S rappresentano la caratteristica in eccesso
\(p\); abbiamo quindi \(p = 2^7 - 1 = 127\).
I 23 bit meno significativi rappresentano la mantissa.
Per i numeri normalizzati abbiamo i seguenti estremi positivi:
\begin{align}
x_{min} &= 2^{-126} \cdot (1,0000000000000000000000{0_2}) = 2^{-126} \cdot 1
= 2^{-126} \cr
x_{max} &= 2^{127} \cdot (1,1111111111111111111111{1_2}) =
2^{127} \cdot (1 - 2^{-23}) = 2^{127} - 2^{104}
\end{align}
Gli stessi estremi in base \(10\) sono:
\begin{align}
x_{min} &= 1,18 \cdot 10^{-38} \cr
x_{max} &= 3,40 \cdot 10^{38}
\end{align}
La precisione è pari a 6-7 cifre significative dopo la virgola.
A questo punto siamo in grado di spiegare la codifica IEEE 754 in
single precision per il numero \(x = 3,625\), mostrata in precedenza. Questo
numero in binario si scrive \(x = 11,101_2\), che normalizzato diventa
\(x_n = 1,1101_2 \cdot 2^1\); la mantissa a \(23\) bit è quindi
\(m = 1101000000000000000000{0_2}\).
La caratteristica è \(E = 1\); in eccesso \(p\) otteniamo allora:
\[ e = E + p = 1 + 127 = 128 = 1000000{0_2} \]
Essendo \(S = 0\), si ha infine la codifica a \(32\) bit:
\[ x = 0 \vert 10000000 \vert 11010000000000000000000 \]
16.4.2 Formato double precision a 64 bit
La Figura 16.4 illustra la struttura del formato in precisione doppia a 64
bit.
Il bit S in posizione 63 rappresenta il segno della mantissa. Gli
11 bit che precedono S rappresentano la caratteristica in eccesso
\(p\); abbiamo quindi \(p = 2^{10} - 1 = 1023\).
I 52 bit meno significativi rappresentano la mantissa.
Per i numeri normalizzati abbiamo i seguenti estremi positivi:
\begin{align}
x_{min} &= 2^{-1022} \cdot (1,00...0{0_2}) = 2^{-1022} \cdot 1
= 2^{-1022} \cr
x_{max} &= 2^{1023} \cdot (1,11...1{1_2}) =
2^{1023} \cdot (1 - 2^{-52}) = 2^{1023} - 2^{971}
\end{align}
Gli stessi estremi in base \(10\) sono:
\begin{align}
x_{min} &= 2,23 \cdot 10^{-308} \cr
x_{max} &= 1,79 \cdot 10^{308}
\end{align}
La precisione è pari a 15-16 cifre significative dopo la virgola.
16.4.3 Formato quadruple precision a 128 bit
La Figura 16.5 illustra la struttura del formato in precisione quadrupla
a 128 bit.
Il bit S in posizione 127 rappresenta il segno della mantissa. I
15 bit che precedono S rappresentano la caratteristica in eccesso
\(p\); abbiamo quindi \(p = 2^{14} - 1 = 16383\).
I 112 bit meno significativi rappresentano la mantissa.
Per i numeri normalizzati abbiamo i seguenti estremi positivi:
\begin{align}
x_{min} &= 2^{-16382} \cdot (1,00...0{0_2}) = 2^{-16382} \cdot 1
= 2^{-16382} \cr
x_{max} &= 2^{16383} \cdot (1,11...1{1_2}) =
2^{16383} \cdot (1 - 2^{-112}) = 2^{16383} - 2^{16271}
\end{align}
Gli stessi estremi in base \(10\) sono:
\begin{align}
x_{min} &= 3,3621 \cdot 10^{-4932} \cr
x_{max} &= 1,1897 \cdot 10^{4932}
\end{align}
La precisione è pari a 34 cifre significative dopo la virgola.
16.4.4 Operazioni raccomandate dallo standard IEEE 754
Lo standard IEEE 754 comprende una serie di operazioni rivolte ai
numeri in floating point; tali operazioni devono essere implementate dalle
librerie matematiche dei linguaggi di programmazione e dai coprocessori
matematici.
In particolare, devono essere forniti i principali operatori aritmetici
(addizione, sottrazione, moltiplicazione e divisione), operatori di
conversione tra formati, operatori di manipolazione del segno (valore
assoluto, negazione, etc), operatori di comparazione, operatori di gestione
dei NaN, operatori di gestione dei flags.
Lo standard IEEE 754 raccomanda anche una serie di operazioni
opzionali che le librerie matematiche dei linguaggi di programmazione e i
coprocessori matematici possono implementare; le principali operazioni
raccomandate sono le seguenti:
\begin{align}
\DeclareMathOperator{\arcsinh}{arcsinh}
\DeclareMathOperator{\arccosh}{arccosh}
\DeclareMathOperator{\arctanh}{arctanh}
& e^x, \quad 2^x, \quad 10^x \cr
& e^x - 1, \quad 2^x - 1, \quad 10^x - 1 \cr
& \ln x, \quad \log_2 x, \quad \log_{10} x \cr
& \ln (1 + x), \quad \log_2 (1 + x), \quad \log_{10} (1 + x) \cr
& \sqrt{x^2 + y^2} \cr
& \sqrt{x} \cr
& (1 + x)^n \cr
& x^{1 \over n} \cr
& x^n, \quad x^y \cr
& \sin x, \quad \cos x, \quad \tan x \cr
& \arcsin x, \quad \arccos x, \quad \arctan x \cr
& \sin(\pi x), \quad \cos(\pi x) \cr
& {{\arctan x} \over \pi} \cr
& \sinh x, \quad \cosh x, \quad \tanh x \cr
& \arcsinh x, \quad \arccosh x, \quad \arctanh x
\end{align}
16.4.5 Metodi di arrotondamento
Lo standard IEEE 754 prevede cinque metodi di arrotondamento da
applicare ai numeri in floating point.
- Round to nearest, ties to even
Il numero in floating point viene arrotondato verso il numero intero più
vicino; ad esempio, \(+7,41327\) viene arrotondato a \(+7,0\), mentre
\(-3,8972\) viene arrotondato a \(-4,0\).
Se il numero in floating point cade a metà tra due numeri interi,
l'arrotondamento avviene verso il numero intero pari più vicino (si ricordi
che un numero intero pari ha il bit meno significativo che vale zero); ad
esempio, \(+7,5\) viene arrotondato a \(+8,0\) e anche \(+8,5\) viene
arrotondato a \(+8,0\).
Si tratta del metodo di arrotondamento predefinito per i numeri in virgola
mobile.
- Round to nearest, ties away from zero
Il numero in floating point viene arrotondato verso il numero intero più
vicino; ad esempio, \(+15,321\) viene arrotondato a \(+15,0\), mentre
\(-1,9826\) viene arrotondato a \(-2,0\).
Se il numero in floating point cade a metà tra due numeri interi,
l'arrotondamento avviene verso il numero intero immediatamente maggiore
per i numeri positivi e verso il numero intero immediatamente minore per
i numeri negativi; ad esempio, \(+8,5\) viene arrotondato a \(+9,0\),
mentre \(-6,5\) viene arrotondato a \(-7\).
Il numero in floating point viene arrotondato al numero intero più vicino
verso lo zero; ad esempio, \(+15,321\) viene arrotondato a \(+15,0\), mentre
\(-1,9826\) viene arrotondato a \(-1,0\).
Come si può notare, questo metodo consiste semplicemente nel troncare la
parte frazionaria del numero in floating point.
Il numero in floating point viene arrotondato al numero intero più vicino
verso \(+\infty\); ad esempio, \(+15,321\) viene arrotondato a \(+16,0\),
mentre \(-1,9826\) viene arrotondato a \(-1,0\).
Il numero in floating point viene arrotondato al numero intero più vicino
verso \(-\infty\); ad esempio, \(+15,321\) viene arrotondato a \(+15,0\),
mentre \(-1,9826\) viene arrotondato a \(-2,0\).
16.4.6 Gestione delle eccezioni
Lo standard IEEE 754 definisce cinque eccezioni per il risultato di
una operazione tra numeri in floating point; ciascuna eccezione produce un
valore predefinito.
Questa eccezione viene generata quando una operazione produce un risultato
non definito matematicamente; ad esempio, \(\sqrt{-1}\) produce un
risultato che non appartiene ad \(\Bbb R\).
Il valore predefinito restituito da questa eccezione è un qNaN.
Questa eccezione viene generata quando una operazione produce un risultato
infinitamente grande; ad esempio, \(3 / 0 = +\infty\), mentre
\(\log_2 {0^+} = -\infty\).
Il valore predefinito restituito da questa eccezione è \(\pm\infty\).
Questa eccezione viene generata quando una operazione produce un risultato più grande
(in valore assoluto) del massimo rappresentabile attraverso il formato che si sta
utilizzando.
Il valore predefinito restituito da questa eccezione dipende dal metodo di
arrotondamento in uso.
Se il risultato è positivo, il valore predefinito è \(+\infty\) per to nearest,
il più grande numero positivo rappresentabile per toward \(-\infty\), \(+\infty\)
per toward \(+\infty\) e il più grande numero positivo rappresentabile per
toward zero.
Se il risultato è negativo, il valore predefinito è \(-\infty\) per to nearest,
\(-\infty\) per toward \(-\infty\), il più grande numero negativo
rappresentabile per toward \(+\infty\) e il più grande numero negativo
rappresentabile per toward zero.
Questa eccezione viene generata quando una operazione produce un risultato più piccolo
(in valore assoluto) del minimo rappresentabile attraverso il formato che si sta
utilizzando.
Il valore predefinito restituito da questa eccezione è un numero denormalizzato; se
tale numero non è rappresentabile in modo esatto, si procede come descritto qui sotto
per l'eccezione Inexact.
Questa eccezione viene generata quando una operazione produce un risultato che non
può essere rappresentato in modo esatto attraverso il formato che si sta utilizzando.
Il valore predefinito restituito da questa eccezione è il risultato arrotondato in
base al metodo di arrotondamento in uso.
Bibliografia
754-2008 - IEEE Standard for Floating-Point Arithmetic
disponibile sul sito ufficiale della
IEEE.org
(è richiesta la registrazione)
In alternativa, si può consultare il documento sui siti web di varie università;
ad esempio:
754-2008 - IEEE Standard for Floating-Point Arithmetic
Université de La Réunion
754-2008 - IEEE Standard for Floating-Point Arithmetic
Universidade Federal de Campina Grande