Vai al contenuto

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:

  1. Dividi i 32 bit in segno (1), esponente (8), mantissa (23).
  2. Segno: 0 → positivo, 1 → negativo.
  3. Esponente: converti gli 8 bit in decimale e sottrai 127.
  4. Mantissa: scrivi 1, seguito dai bit della mantissa (gli zeri finali si possono togliere) e convertila.
  5. 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:

  1. Segno: 0 se positivo, 1 se negativo. Da qui in poi lavora con il valore assoluto.
  2. Converti in binario parte intera (divisioni successive) e parte frazionaria (moltiplicazioni successive), come nella lezione 02.
  3. Normalizza: sposta la virgola subito dopo il primo 1. Ogni posto verso sinistra aggiunge 1 all'esponente E (ogni posto verso destra toglie 1).
  4. Esponente: calcola E + 127 e scrivilo in binario su 8 bit.
  5. 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. ✓

Verifica