Lezione 04 · Rappresentazione dei numeri reali in binario¶
Cosa impari
- che cos'è la rappresentazione in virgola mobile (floating point): segno, mantissa, base ed esponente;
- che cosa vuol dire mantissa normalizzata;
- perché un calcolatore rappresenta i numeri reali solo in modo approssimato, e che cosa sono errore di approssimazione, overflow e underflow;
- come sono fatti i formati IEEE 754 a singola (32 bit) e doppia precisione (64 bit);
- a convertire un numero reale in IEEE 754 singola precisione e da IEEE 754 a decimale.
Con gli interi ci siamo fermati: ma un ingegnere lavora soprattutto con numeri reali, come 9,81 m/s² o 0,000012 mm. In un intervallo di numeri reali, anche piccolissimo, ci sono infiniti valori, mentre un calcolatore ha un numero finito di bit. In questa lezione vedi come si risolve il problema e quali errori comporta.
La notazione scientifica: virgola mobile¶
Un numero reale si rappresenta in notazione scientifica, detta anche virgola mobile o floating point:
r = ± m × bᵉ
| Simbolo | Nome | A che cosa serve |
|---|---|---|
| m | mantissa (numero frazionario) | il suo numero di cifre fissa la precisione |
| b | base (fissata: 10 o 2) | non va memorizzata |
| e | esponente o caratteristica (intero) | fissa l'ampiezza dell'intervallo di valori |
Lo stesso valore si può scrivere in infiniti modi, perché la virgola "si sposta" cambiando l'esponente:
- 48000 = 48 × 10³ = 4,8 × 10⁴ = 0,48 × 10⁵ …
- (0,1011)₂ = (1011)₂ × 2⁻⁴ = (1,011)₂ × 2⁻¹ …
Per avere una sola scrittura si usa la mantissa normalizzata:
1 ≤ |m| < b
cioè la prima cifra prima della virgola è diversa da zero e la mantissa è minore della base. Nell'esempio: 4,8 × 10⁴ e (1,011)₂ × 2⁻¹.
In binario la prima cifra è sempre 1
In base 2 l'unica cifra diversa da zero è 1. Quindi una mantissa binaria normalizzata ha sempre la forma 1,xxxxx. Questo fatto sarà molto utile tra poco.
Perché i reali sono approssimati¶
I numeri reali formano un continuo: tra due numeri qualsiasi ce ne sono infiniti altri. Con un numero finito di bit si può rappresentare solo un insieme finito di valori X₀, X₁, …, Xₙ. Questo passaggio da continuo a finito si chiama discretizzazione.
- Solo i valori Xᵢ sono rappresentati esattamente.
- Ogni altro valore x compreso tra Xᵢ e Xᵢ₊₁ viene sostituito con uno dei due estremi (troncando, o arrotondando al più vicino).
La differenza tra il valore vero e quello memorizzato è l'errore di approssimazione.
L'errore massimo¶
Pensa a una calcolatrice con quattro cifre decimali dopo la virgola, che arrotonda l'ultima cifra.
| Numero | Arrotondato | Errore |
|---|---|---|
| 0,00347 | 0,0035 | 0,00003 = 0,3 × 10⁻⁴ |
| 0,000348 | 0,0003 | 0,000048 = 0,48 × 10⁻⁴ |
| 0,00987 | 0,0099 | 0,00003 = 0,3 × 10⁻⁴ |
| 0,000987 | 0,0010 | 0,000013 = 0,13 × 10⁻⁴ |
L'errore non supera mai mezza unità dell'ultima cifra, cioè 0,5 × 10⁻⁴. In generale, con base b e m cifre dopo la virgola:
errore massimo ε = 0,5 × b⁻ᵐ
dove b⁻ᵐ è il valore di un'unità nell'ultima posizione.
Un esempio: la calcolatrice in virgola mobile¶
Immagina una calcolatrice decimale con:
- mantissa di 5 cifre, normalizzata: da 1,0000 a 9,9999;
- base b = 10;
- esponente di 2 cifre più il segno: da −99 a +99.
| Numero | Rappresentazione |
|---|---|
| 0,384 | 3,8400 × 10⁻¹ |
| 13456700000 | 1,3457 × 10¹⁰ (arrotondato: la sesta cifra si perde) |
| 64350 | 6,4350 × 10⁴ |
| 333 | 3,3300 × 10² |
| 0,0048 | 4,8000 × 10⁻³ |
| 0,0000001 | 1,0000 × 10⁻⁷ |
Gli intervalli non sono tutti uguali. Tra due numeri rappresentabili consecutivi c'è sempre un'unità sull'ultima cifra della mantissa, ma il suo valore dipende dall'esponente:
- vicino al massimo, tra 9,9998 × 10⁹⁹ e 9,9999 × 10⁹⁹, c'è un "buco" di 10⁹⁵;
- vicino allo zero, tra 1,0000 × 10⁻⁹⁹ e 1,0001 × 10⁻⁹⁹, c'è un buco di 10⁻¹⁰³.
I numeri rappresentabili sono fitti vicino allo zero e radi lontano dallo zero. L'errore relativo però resta circa lo stesso.
Overflow e underflow¶
| Condizione | Quando succede | Calcolatrice di esempio |
|---|---|---|
| Overflow | il numero è troppo grande in valore assoluto | x > 9,9999 × 10⁹⁹ oppure x < −9,9999 × 10⁹⁹ |
| Underflow | il numero è troppo vicino a zero e viene confuso con lo zero | −1,0000 × 10⁻⁹⁹ < x < 1,0000 × 10⁻⁹⁹ (escluso lo zero) |
Le operazioni in virgola mobile¶
Con la virgola mobile le operazioni si complicano e possono introdurre nuovi errori.
Somma e sottrazione richiedono di allineare gli esponenti: il numero con esponente più piccolo viene riscritto con l'esponente dell'altro, e così perde cifre.
Cifre che spariscono
1,2345 × 10¹ + 9,8765 × 10⁴ = 0,0012 × 10⁴ + 9,8765 × 10⁴ = 9,8777 × 10⁴
Per allineare, 12,345 è diventato 0,0012 × 10⁴: le cifre 345 sono andate perse. Il risultato esatto sarebbe 98777,345.
Sottrarre numeri quasi uguali
1,2345 × 10¹ − 1,2344 × 10¹ = 0,0001 × 10¹ = 1,0000 × 10⁻³
Il risultato ha una sola cifra significativa vera: gli zeri aggiunti dalla normalizzazione non portano informazione.
Prodotto e divisione trattano separatamente mantisse ed esponenti: le mantisse si moltiplicano (o dividono), gli esponenti si sommano (o sottraggono).
123 × 10⁰ × 678 × 10⁻² = (123 × 678) × 10⁽⁰⁻²⁾ = 83394 × 10⁻²
Attenzione alla divisione per numeri molto piccoli: il risultato può diventare enorme e andare in overflow.
La virgola mobile nel calcolatore¶
In binario il numero normalizzato ha la forma:
± 1,xxxxxx × 2ʸʸʸʸ
Il calcolatore memorizza solo ciò che serve:
| Parte | Come si memorizza |
|---|---|
| Segno | 1 bit: 0 se positivo, 1 se negativo |
| "1," | non si memorizza: è sempre 1, si chiama bit implicito (o nascosto) |
| xxxxxx | le cifre dopo la virgola: è la mantissa memorizzata |
| "× 2" | non si memorizza: la base è sempre 2 |
| yyyy | l'esponente, memorizzato per eccesso (lo hai visto nella lezione 03) |
Lo standard IEEE 754¶
Fino agli anni '80 ogni costruttore aveva un suo formato per i numeri reali. Nel 1985 nasce lo standard IEEE 754, oggi usato da quasi tutti i calcolatori. Definisce tre formati:
- singola precisione: 32 bit;
- doppia precisione: 64 bit;
- precisione estesa: 80 bit.
Singola precisione (32 bit)¶
| S | Esponente | Mantissa |
|---|---|---|
| 1 bit (bit 31) | 8 bit (bit 30 … 23) | 23 bit (bit 22 … 0) |
- Esponente: 8 bit in eccesso 127. Gli esponenti normali vanno da −126 a +127. Le parole 00000000 e 11111111 (che corrisponderebbero a −127 e +128) sono riservate a casi speciali.
- Mantissa: 23 bit, sono le cifre dopo "1,". L'1 iniziale è sottinteso.
- Precisione: circa 7 cifre decimali (23 / 3,3 ≈ 7: servono circa 3,3 bit per ogni cifra decimale).
Valore = (−1)ˢ × 1,mantissa × 2^(esponente − 127)
I valori di esponente e mantissa dividono i numeri in categorie: zeri, numeri normalizzati, numeri denormalizzati (molto vicini a zero), infiniti e NaN (Not a Number, ad esempio il risultato di 0/0). L'esponente distingue le categorie in modo principale, la mantissa in modo secondario.
| Esponente | Mantissa | Significato |
|---|---|---|
| 00000000 | tutta a 0 | zero (+0 o −0 a seconda del segno) |
| 00000000 | non nulla | numero denormalizzato |
| da 00000001 a 11111110 | qualsiasi | numero normalizzato (il caso degli esercizi) |
| 11111111 | tutta a 0 | infinito (+∞ o −∞) |
| 11111111 | non nulla | NaN |
Doppia precisione (64 bit)¶
| S | Esponente | Mantissa |
|---|---|---|
| 1 bit (bit 63) | 11 bit (bit 62 … 52) | 52 bit (bit 51 … 0) |
- Esponente: 11 bit in eccesso 1023, esponenti normali da −1022 a +1023. Le parole 0 e 2047 (tutti 0 e tutti 1) sono riservate.
- Mantissa: 52 bit, con l'1 iniziale sottinteso.
- Precisione: circa 15 cifre decimali (52 / 3,3 ≈ 15).
Python usa la doppia precisione
Il tipo float di Python è un numero IEEE 754 a doppia precisione. Per questo print(0.1 + 0.2) stampa 0.30000000000000004: né 0,1 né 0,2 si possono scrivere con un numero finito di bit.
Da IEEE 754 a decimale¶
Procedimento:
- Dividi i 32 bit in segno (1), esponente (8), mantissa (23).
- Segno: 0 → positivo, 1 → negativo.
- Esponente: converti gli 8 bit in decimale e sottrai 127.
- Mantissa: scrivi 1, seguito dai bit della mantissa (gli zeri finali si possono togliere) e convertila.
- Calcola (−1)ˢ × M × 2ᴱ.
Esempio svolto: 1 10000001 01000000000000000000000
| Parte | Bit | Calcolo | Valore |
|---|---|---|---|
| Segno | 1 | negativo | |
| Esponente | 10000001 | 128 + 1 = 129; 129 − 127 | E = 2 |
| Mantissa | 0100…0 | (1,01)₂ = 1 + 0 × 2⁻¹ + 1 × 2⁻² | M = 1,25 |
Valore = −1,25 × 2² = −1,25 × 4 = −5
Esempio svolto: 0 10000011 10011000000000000000000
| Parte | Bit | Calcolo | Valore |
|---|---|---|---|
| Segno | 0 | positivo | |
| Esponente | 10000011 | 128 + 2 + 1 = 131; 131 − 127 | E = 4 |
| Mantissa | 10011000…0 | (1,10011)₂ = 1 + 2⁻¹ + 2⁻⁴ + 2⁻⁵ | M = 1,59375 |
Valore = 1,59375 × 2⁴ = 1,59375 × 16 = 25,5
Scorciatoia: moltiplicare per 2⁴ sposta la virgola di 4 posti a destra. (1,10011)₂ × 2⁴ = (11001,1)₂ = 16 + 8 + 1 + 0,5 = 25,5.
Verifica con Python (facoltativo). Incolla i 32 bit (puoi lasciare gli spazi) e il programma rifà i passaggi. Funziona per i numeri normalizzati, cioè quelli degli esercizi.
parola = input('32 bit (spazi ammessi): ').replace(' ', '').replace('|', '')
s = parola[0]
esponente = parola[1:9]
mantissa = parola[9:]
E = int(esponente, 2) - 127 # si toglie l'eccesso 127
M = 1 # il bit implicito
peso = 0.5
for bit in mantissa:
if bit == '1':
M = M + peso
peso = peso / 2
valore = M * 2 ** E
if s == '1':
valore = -valore
print('Segno:', '-' if s == '1' else '+')
print('Esponente:', esponente, '=', int(esponente, 2), '- 127 =', E)
print('Mantissa: M =', M)
print('Valore:', valore)
Da decimale a IEEE 754¶
Procedimento:
- Segno: 0 se positivo, 1 se negativo. Da qui in poi lavora con il valore assoluto.
- Converti in binario parte intera (divisioni successive) e parte frazionaria (moltiplicazioni successive), come nella lezione 02.
- Normalizza: sposta la virgola subito dopo il primo 1. Ogni posto verso sinistra aggiunge 1 all'esponente E (ogni posto verso destra toglie 1).
- Esponente: calcola E + 127 e scrivilo in binario su 8 bit.
- Mantissa: prendi le cifre dopo "1," e completale con zeri a destra fino a 23 bit.
Esempio svolto: 8,5
Passo 1. 8,5 è positivo → s = 0.
Passo 2. 8 = (1000)₂; 0,5 × 2 = 1,0 → 0,5 = (0,1)₂. Quindi 8,5 = (1000,1)₂.
Passo 3. Sposta la virgola di 3 posti a sinistra: (1000,1)₂ = (1,0001)₂ × 2³ → E = 3.
Passo 4. 3 + 127 = 130 = 128 + 2 = (10000010)₂.
Passo 5. Dopo "1," ci sono 0001 → mantissa 0001 seguito da 19 zeri.
| S | Esponente | Mantissa |
|---|---|---|
| 0 | 10000010 | 00010000000000000000000 |
Esempio svolto: −67,25
Passo 1. Negativo → s = 1. Continua con 67,25.
Passo 2. Parte intera 67:
| Divisione | Quoziente | Resto |
|---|---|---|
| 67 : 2 | 33 | 1 |
| 33 : 2 | 16 | 1 |
| 16 : 2 | 8 | 0 |
| 8 : 2 | 4 | 0 |
| 4 : 2 | 2 | 0 |
| 2 : 2 | 1 | 0 |
| 1 : 2 | 0 | 1 |
Resti dal basso: 67 = (1000011)₂.
Parte frazionaria 0,25: 0,25 × 2 = 0,5 → 0; 0,5 × 2 = 1,0 → 1. Quindi 0,25 = (0,01)₂.
67,25 = (1000011,01)₂.
Passo 3. Sposta la virgola di 6 posti a sinistra: (1,00001101)₂ × 2⁶ → E = 6.
Passo 4. 6 + 127 = 133 = 128 + 4 + 1 = (10000101)₂.
Passo 5. Dopo "1,": 00001101, poi zeri fino a 23 bit.
| S | Esponente | Mantissa |
|---|---|---|
| 1 | 10000101 | 00001101000000000000000 |
Verifica con Python (facoltativo). Il programma segue gli stessi cinque passi e stampa i 32 bit.
x = float(input('Numero reale da codificare (es. 8.5): '))
if x == 0:
print('Lo zero ha una codifica speciale: tutti i bit a 0')
else:
# 1) segno
if x < 0:
s = '1'
x = -x
else:
s = '0'
# 2) parte intera (divisioni successive) e parte frazionaria (moltiplicazioni successive)
intera = int(x)
frazione = x - intera
bit_interi = ''
while intera > 0:
bit_interi = str(intera % 2) + bit_interi
intera = intera // 2
bit_fraz = ''
while frazione != 0 and len(bit_fraz) < 200:
frazione = frazione * 2
if frazione >= 1:
bit_fraz = bit_fraz + '1'
frazione = frazione - 1
else:
bit_fraz = bit_fraz + '0'
print('In binario:', (bit_interi or '0') + ',' + (bit_fraz[:30] or '0'), '...' if len(bit_fraz) > 30 else '')
# 3) normalizzazione: 1,xxxx * 2^E
if bit_interi != '':
E = len(bit_interi) - 1
dopo_virgola = bit_interi[1:] + bit_fraz
else:
primo_uno = bit_fraz.find('1')
E = -(primo_uno + 1)
dopo_virgola = bit_fraz[primo_uno + 1:]
print('Normalizzato: 1,' + (dopo_virgola[:30] or '0'), '* 2^' + str(E))
# 4) esponente in eccesso 127 su 8 bit
ecc = E + 127
bit_esp = ''
for i in range(8):
bit_esp = str(ecc % 2) + bit_esp
ecc = ecc // 2
# 5) mantissa: i primi 23 bit dopo la virgola, completati con zeri
mantissa = (dopo_virgola + '0' * 23)[:23]
print('Esponente:', E, '+ 127 =', E + 127, '->', bit_esp)
print('Risultato:', s, bit_esp, mantissa)
Prova con 8.5, -67.25 e 0.1. Il programma tronca la mantissa dopo 23 bit; lo standard invece arrotonda l'ultimo bit, quindi per numeri non esatti come 0,1 l'ultimo bit può essere diverso. Negli esercizi d'esame il numero è sempre esatto e il problema non si pone.
Tool · IEEE 754
Il tool segue gli stessi cinque passi, in singola o doppia precisione, e nella seconda scheda decodifica una parola di 32 bit. Se il numero non è esatto mostra sia la mantissa troncata (come negli esercizi a mano) sia quella arrotondata dallo standard.
Errori frequenti negli esercizi¶
| Errore | Perché è sbagliato | Come si corregge |
|---|---|---|
| Scrivere nella mantissa anche l'1 prima della virgola | l'1 è implicito, non si memorizza | la mantissa contiene solo le cifre dopo "1," |
| Scrivere E in binario senza aggiungere 127 | l'esponente è memorizzato in eccesso 127 | calcola E + 127, poi converti su 8 bit |
| In decodifica, dimenticare di togliere 127 | l'esponente risulta enorme | E = valore degli 8 bit − 127 |
| In decodifica, dimenticare l'1 implicito | la mantissa vale meno di 1 | M = 1,mantissa |
| Sbagliare il verso dello spostamento della virgola | E ha il segno sbagliato | virgola verso sinistra → E positivo; verso destra → E negativo |
| Mettere gli zeri di completamento a sinistra della mantissa | cambia il valore | gli zeri si aggiungono a destra, fino a 23 bit |
| Contare male i bit | la parola non è di 32 bit | controlla: 1 + 8 + 23 = 32 |
Esercizi¶
Esercizio 1 · Da IEEE 754 a decimale. Determina il valore decimale di 1 10000010 10110100000000000000000.
Soluzione
| Parte | Bit | Calcolo | Valore |
|---|---|---|---|
| Segno | 1 | negativo | |
| Esponente | 10000010 | 128 + 2 = 130; 130 − 127 | E = 3 |
| Mantissa | 1011010…0 | (1,101101)₂ = 1 + 0,5 + 0,125 + 0,0625 + 0,015625 | M = 1,703125 |
Valore = −1,703125 × 2³ = −1,703125 × 8 = −13,625
Con la scorciatoia: (1,101101)₂ × 2³ = (1101,101)₂ = 13 + 0,5 + 0,125 = 13,625, con il segno meno.
Esercizio 2 · Da decimale a IEEE 754. Rappresenta 40,125 in IEEE 754 a singola precisione.
Soluzione
Segno: positivo → 0.
Parte intera 40:
| Divisione | Quoziente | Resto |
|---|---|---|
| 40 : 2 | 20 | 0 |
| 20 : 2 | 10 | 0 |
| 10 : 2 | 5 | 0 |
| 5 : 2 | 2 | 1 |
| 2 : 2 | 1 | 0 |
| 1 : 2 | 0 | 1 |
Resti dal basso: 40 = (101000)₂.
Parte frazionaria 0,125:
| Frazione | × 2 | Parte intera |
|---|---|---|
| 0,125 | 0,25 | 0 |
| 0,25 | 0,5 | 0 |
| 0,5 | 1,0 | 1 |
0,125 = (0,001)₂, quindi 40,125 = (101000,001)₂.
Normalizzazione: virgola 5 posti a sinistra → (1,01000001)₂ × 2⁵, E = 5.
Esponente: 5 + 127 = 132 = 128 + 4 = (10000100)₂.
Mantissa: 01000001 + 15 zeri.
| S | Esponente | Mantissa |
|---|---|---|
| 0 | 10000100 | 01000001000000000000000 |
Esercizio 3 · Un numero minore di 1 (come allo scritto). Rappresenta −0,75 in IEEE 754 a singola precisione. Poi decodifica il risultato per controllarlo.
Soluzione
Segno: negativo → 1.
Binario di 0,75: 0,75 × 2 = 1,5 → 1; 0,5 × 2 = 1,0 → 1. Quindi 0,75 = (0,11)₂.
Normalizzazione: il primo 1 è dopo la virgola, quindi la virgola si sposta di 1 posto a destra: (0,11)₂ = (1,1)₂ × 2⁻¹, E = −1.
Esponente: −1 + 127 = 126 = 64 + 32 + 16 + 8 + 4 + 2 = (01111110)₂. Nota lo 0 iniziale: gli esponenti negativi danno parole minori di 127.
Mantissa: dopo "1," c'è 1 → 1 seguito da 22 zeri.
| S | Esponente | Mantissa |
|---|---|---|
| 1 | 01111110 | 10000000000000000000000 |
Controllo: E = 126 − 127 = −1; M = (1,1)₂ = 1,5; valore = −1,5 × 2⁻¹ = −0,75. ✓