Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
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.
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).
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):
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:
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:
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
:
5 -3 5 ⋅ 10−3 0.005
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:
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 ?
ESERCIZIO IN LABORATORIO:
qual è il valore della costante BIAS per un numero a virgola mobile a precisione doppia?
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);
#include <stdio.h>
#include <float.h>
int main(){
return 0;
}
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) ).
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);
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":
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
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
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).