Sei sulla pagina 1di 7

Numeri a virgola mobile

Autore: Manuel Dalcastagnè. Questo materiale è rilasciato secondo la licenza CC Attribution 3.0 Unported
(http://creativecommons.org/licenses/by/3.0/ (http://creativecommons.org/licenses/by/3.0/)).

Per numeri a virgola mobile (floating point numbers) si intendono i numeri binari utilizzati da un computer per
rappresentare i numeri in base decimale. Come abbiamo già visto, i bit dell'architettura di una CPU definiscono
il numero di bit utilizzabili dai registri utilizzati dalla CPU, e di conseguenza anche i numeri usati da una CPU
devono essere rappresentati in base binaria.

Errore di arrotondamento in base binaria di un numero in base decimale

Numeri interi

Un numero intero in base decimale è sempre rappresentabile in base binaria in modo esatto.

La conversione avviene dividendo progressivamente il numero per 2 fino a quando non si arriva ad avere un
quoziente pari a 1. La conversione in binario si ottiene concatenando al rovescio i resti delle divisioni fatte
durante il processo:

13 = 13 / 2 = 6 con resto di 1
6 / 2 = 3 con resto di 0
3 / 2 = 1 con resto di 1
1 / 2 = 0 con resto di 1

e quindi1310 = 11012 . Per passare da una rappresentazione binaria ad una decimale, ogni bit viene
moltiplicato per una potenza di 2, dove l'esponente è dato dalla posizione del bit considerato. Il calcolo viene
fatto da destra a sinistra, dal bit meno significativo a quello più significativo (la posizione parte da 0).

Nell'esempio precedente: 11012 = 20 ⋅ 1 + 21 ⋅ 0 + 22 ⋅ 1 + 23 ⋅ 1 = 1 + 0 + 4 + 8 = 1310 .


Numeri reali

Un numero reale in base decimale invece non è sempre rappresentabile in base binaria in modo esatto, in
quanto è costituito da una parte intera e una parte frazionaria. Come definito nella precedente sezione la parte
intera è sempre rappresentabile in modo esatto, mentre per quanto riguarda la parte frazionaria non è così.

La parte frazionaria si converte moltiplicandola progressivamente per 2 fino a quando non si ottiene
esattamente 1 o un risultato già ottenuto in precedenza. Inoltre, se il prodotto ottenuto è maggiore di 1, si toglie
1 prima di procedere al prodotto successivo. La conversione in binario si ottiene concatenando le parti intere
dei prodotti ottenuti senza togliere 1 (nello stesso ordine in cui sono state fatte le moltiplicazioni):

0.625 = 0.625 * 2 = 1.25 (>1, quindi 1.25 - 1 = 0.25)


0.25 * 2 = 0.50
0.50 * 2 = 1.0

e perciò 0.62510 = 0.1012 . Anche in questo caso, per passare dalla rappresentazione binaria a quella
decimale, ogni bit viene moltiplicato per una potenza di 2, ma l'esponente è dato dalla posizione negativa del bit
considerato. Il calcolo viene fatto da sinistra a destra.
Nell'esempio precedente:
0.1012 = 2−1 ⋅ 1 + 2−2 ⋅ 0 + 2−3 ⋅ 1 = 12 ⋅ 1 + 14 ⋅ 0 + 18 ⋅ 1 = 12 + 18 = 58 = 0.62510
Di conseguenza, un computer può rappresentare in modo esatto solo i numeri reali la cui parte frazionaria
corrisponde alla somma di una o più frazioni il cui denominatore è dato da una potenza di 2, e questo causa un
errore di arrotondamento. Vediamo un esempio:

0.1 = 0.1 * 2 = 0.2


0.2 * 2 = 0.4
0.4 * 2 = 0.8
0.8 * 2 = 1.6 (>1)
0.6 * 2 = 1.2 (>1)
0.2 * 2 = 0.4

e quindi 0.110 = 0.0001102 . Tuttavia, 0.0001102 = 2−4 ⋅ 1 + 2−5 ⋅ 1 = 161 + 321 = 323 = 0.0937510 .
Perciò, se un computer usasse 6 bit per rappresentare 0.1, ci sarebbe un errore di arrotondamento uguale a
0.110 − 0.0937510 = 0.0062510 .
ESERCIZI IN LABORATORIO:

qual è la rappresentazione binaria di 21?


qual è la rappresentazione binaria di 0.84375?
qual è la rappresentazione binaria di 25.125?

Notazione scientifica in base decimale


Gli esseri umani usano la notazione scientifica per rappresentare i numeri in base decimale in un formato
compatto attraverso la sintassi 𝑚 ⋅ 10𝑛 𝑚 𝑛
, dove è la mantissa ed è l'esponente. La mantissa definisce i
caratteri che compongono il numero, mentre l'esponente indica la posizione della virgola.

Un numero come 350 può quindi essere espresso come:

35000 ⋅ 10−1−2
3500 ⋅ 100
350 ⋅ 10 1
35.0 ⋅ 102
3.5 ⋅ 10
Nella notazione scientifica normalizzata (o standard), l'esponente viene scelto in modo tale che il valore
𝑖
assoluto della parte intera della mantissa sia 0 < |𝑖| < 10
:

Mantissa Esponente Notazione scientifica normalizzata Numero a virgola fissa

3.5 2 3.5 ⋅ 102 350

1.5 4 1.5 ⋅ 104 15000

-2.001 2 −2.001 ⋅ 102 -200.1

5 -3 5 ⋅ 10−3 0.005

Notazione scientifica in base binaria (IEEE 754)


La maggior parte dei computer moderni usa lo standard IEEE 754 per rappresentare i numeri in base binaria,
seguendo un formato molto simile a quello della notazione scientifica usato dagli esseri umani per i numeri in
base decimale.

Un numero a virgola mobile viene infatti rappresentato in base binaria salvando in memoria

𝑠
segno , dove 0 e 1 indicano rispettivamente un numero positivo e un numero negativo
esponente 𝑛
mantissa 𝑚
e il valore del rispettivo numero decimale è ottenuto come −1𝑠 ⋅ (1.𝑚)10 ⋅ 2𝑛
10 , in quanto lo IEEE 754
normalizza i numeri a virgola mobile in modo che inizino sempre con 1 e salva in memoria solo i bit rimanenti.
Secondo questo standard, la rappresentazione dei numeri a virgola mobile in base binaria avviene usando 32,
64 o 128 bit (4, 8 e 16 byte) in base alla precisione di rappresentazione richiesta:

Tipo Bit totali Bit segno Bit mantissa Bit dell'esponente

Precisione singola (float) 32 1 23 8

Precisione doppia (double) 64 1 52 11

Precisione doppia estesa (long double) 128 1 112 15

Inoltre, lo standard IEEE 754 rappresenta l'esponente basandosi sul numero totale di bit 𝑛𝑏𝑖𝑡
che vengono usati
per l'esponente, calcolando il valore dell'esponente come n - BIAS, dove BIAS è una costante definita come
2𝑛𝑏𝑖𝑡 −2 . Ad esempio, nei float l'esponente è rappresentato da 8 bit e permette di rappresentare 256 numeri; in
2 28 −2 = 254 = 127.
questo caso BIAS =
2 2
ESEMPIO: Secondo lo standard IEEE 754, a quale numero decimale corrisponde il numero a virgola mobile a
precisione singola 00111110001000000000000000000000 ?

s = 0, quindi il numero è positivo


esponente = 011111002 0 −2
= 124, quindi esponente - BIAS = 124 - 127 = -3
mantissa = 1.010⋯002 1 ⋅ 2 + 1 ⋅ 2
= = 1 + 0.25 = 1.2510
E quindi il rispettivo numero decimale si ottiene come 1.2510 ⋅ 2 −3 = 1.25 ⋅ 13 = 0.15625
2

Author: Vectorization: Stannered, CC BY-SA 3.0 (https://commons.wikimedia.org/w/index.php?curid=3357169)

ESERCIZIO IN LABORATORIO:

qual è il valore della costante BIAS per un numero a virgola mobile a precisione doppia?

Errore di arrotondamento con lo standard IEEE 754


Anche lo standard IEEE 754 è soggetto all'errore di arrotondamento. Se un numero a virgola mobile non è
perfettamente rappresentabile in base binaria, il valore salvato in memoria corrisponde al numero
corrispondente all'approssimazione binaria più vicina:
float value_float = 0.1f;
double value_double = 0.1;
long double value_long = 0.1l;

printf("%.2f\n", value_float);
printf("%.8f\n", value_float);
printf("%.16f\n", value_float);
printf("%.32f\n\n", value_float);

printf("%.2lf\n", value_double);
printf("%.8lf\n", value_double);
printf("%.16lf\n", value_double);
printf("%.32lf\n\n", value_double);

printf("%.2Lf\n", value_long);
printf("%.8Lf\n", value_long);
printf("%.16Lf\n", value_long);
printf("%.32Lf\n\n", value_long);

restituisce in output

0.10
0.10000000
0.1000000014901161
0.10000000149011611938476562500000

0.10
0.10000000
0.1000000000000000
0.10000000000000000555111512312578

0.10
0.10000000
0.1000000000000000
0.10000000000000000000135525271561

Nota: %f, %lf e %Lf sono i segnaposto usati per stampare rispettivamente un valore di tipo float, double
e long double

Essendo che i numeri a virgola fissa sono rappresentati usando un numero finito di bit, anche la loro precisione
è finita. Se un numero non è rappresentabile in forma esatta entro il numero di bit usato per rappresentare in
memoria il tipo utilizzato, il conseguente arrotondamento causa un errore di arrotondamento dovuto alla perdita
di informazione:
double d1 = 1.0;
printf("%.2lf\n", d1);
printf("%.40lf\n", d1);

double d2 = 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1;
printf("%.2lf\n", d2);
printf("%.40lf\n", d2);

printf("Errore di arrotondamento: %.2lf\n", d1 - d2);


printf("Errore di arrotondamento: %.40lf\n\n", d1 - d2);

Intervallo di valori rappresentabili


Per sapere quale è l'intervallo di valori rappresentabili su un computer in base al tipo utilizzato, è possibile
consultare le costanti dichiarate nello header relativo. Ad esempio, per i numeri a precisione singola si fa
riferimento a float.h:

#include <stdio.h>
#include <float.h>

int main(){

// minimum and maximum values representable by a float


printf("FLT_MIN = %e\n", FLT_MIN);
printf("FLT_MAX = %e\n\n", FLT_MAX);

// minimum and maximum values representable by a double


printf("DBL_MIN = %e\n", DBL_MIN);
printf("DBL_MAX = %e\n\n", DBL_MAX);

// minimum and maximum values representable by a long double


printf("LDBL_MAX = %Le\n", LDBL_MAX);
printf("LDBL_MAX = %Le\n\n", LDBL_MAX);

return 0;
}

che può avere il seguente output (dipende dall'hardware):

FLT_MIN = 1.175494e-38
FLT_MAX = 3.402823e+38

DBL_MIN = 2.225074e-308
DBL_MAX = 1.797693e+308

LDBL_MAX = 1.189731e+4932
LDBL_MAX = 1.189731e+4932

Nota: %e e %Le si usano rispettivamente per stampare float/double e long double usando la notazione
scientifica in base decimale
Lo stesso tipo di informazioni è disponibile anche per altri formati ( vedere
https://en.cppreference.com/w/c/types/limits (https://en.cppreference.com/w/c/types/limits) ).

Confronti tra numeri reali


Regola numero 1 del club dei numeri reali: non confrontare mai due numeri reali usando gli operatori di
uguaglianza e disuguaglianza.

L'errore di arrotondamento può causare problemi in confronti basati su operatori relazionali, in particolare == e
!=. Basti considerare:

double d1 = 1.0;
double d2 = 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1;

if(d1 == d2)
printf("%lf e %lf sono uguali\n", d1, d2);
else
printf("%lf e %lf non sono uguali\n", d1, d2);

l'output non è esattamente quello che una persona comune si aspetterebbe.

Confronti basati su un errore assoluto e un errore relativo

Una possibile soluzione consiste nel verificare se due numeri sono "sufficientemente uguali". Si definisce un
𝜖
errore assoluto positivo sufficientemente piccolo (la grandezza dipende dal problema da risolvere), e se la
𝜖
differenza tra i valori assoluti di due numeri è minore o uguale di , allora i numeri sono "sufficientemente
uguali":

double epsilon_assoluto = 0.00000001;

if(fabs(d1 - d2) <= epsilon_assoluto)


printf("%lf e %lf sono uguali\n", d1, d2);
else
printf("%lf e %lf non sono uguali\n", d1, d2);

Tuttavia, questo approccio non funziona in tutti i casi in quanto non tiene conto della scala dei numeri
confrontati. Perciò è possibile che il confronto abbia un risultato errato. Un miglioramento è dato dall'uso di un
errore relativo basato sul numero col valore assoluto più alto

double epsilon_relativo = 0.01;

if(fabs(d1 - d2) <= fmax(fabs(d1), fabs(d2)) * epsilon_relativo)


printf("%lf e %lf sono uguali\n", d1, d2);
else
printf("%lf e %lf non sono uguali\n", d1, d2);

Anche questo codice ha delle limitazioni, in quanto se i numeri sono prossimi allo zero allora il confronto può
nuovamente risultare errato. Combinando le due soluzioni proposte, si può arrivare ad una soluzione
ulteriormente migliorata:
if(fabs(d1 - d2) <= epsilon_assoluto)
printf("%lf e %lf sono uguali\n", d1, d2);
else
if(fabs(d1 - d2) <= fmax(fabs(d1), fabs(d2)) * epsilon_relativo)
printf("%lf e %lf sono uguali\n", d1, d2);
else
printf("%lf e %lf non sono uguali\n", d1, d2);

Persino quest'ultima versione non è perfetta, infatti è possibile trovare dei numeri il cui confronto risulterebbe
erroneo. Tuttavia copre molti casi e, a meno che non sia necessario scrivere del codice dove la massima
precisione è fondamentale, può essere una scelta "sufficientemente buona".

Esercizio 16
Definire una funzione che permetta di convertire in formato binario un numero intero decimale. L'output della
funzione deve essere come nell'esempio mostrato. Ad esempio, nel caso intero, con input 13:

13 = 13 / 2 = 6 con resto di 1
6 / 2 = 3 con resto di 0
3 / 2 = 1 con resto di 1
1 / 2 = 0 con resto di 1

Leggere l'input da passare alla funzione nel main del programma.

Esercizio 17
Scrivere un programma in C che chieda in input un numero long double. Calcolare l'errore di arrotondamento
che si ottiene sommando il numero letto in input 100, 1000 e 10000 volte a seconda del tipo di variabile usata
per fare la somma (float, double e long double). Per farlo, definire una funzione che prenda in input il numero
letto e il numero di volte da sommare tale numero, e stampi in output le somme e i rispettivi errori di
arrotondamento (rispetto alla somma long double).

Potrebbero piacerti anche