Come viene memorizzato un float
IEEE 754 memorizza un numero come segno × 1.frazione × 2esponente, cioè in notazione scientifica binaria. Un float a 32 bit ha 1 bit di segno, 8 di esponente e 23 di frazione; un double a 64 bit ne ha 1, 11 e 52. L’esponente è memorizzato con un bias (127 o 1023) per non avere bisogno di un proprio bit di segno, e l’1 iniziale della mantissa non viene nemmeno memorizzato, perché nei numeri normali c’è sempre.
| Segno | Esponente | Frazione | Bias | Cifre decimali | |
|---|---|---|---|---|---|
| float32 (binary32) | 1 | 8 | 23 | 127 | ~7 |
| float64 (binary64) | 1 | 11 | 52 | 1023 | ~16 |
Perché 0.1 + 0.2 non fa 0.3
0.1 non ha un’espansione binaria finita, così come 1/3 non ce l’ha in decimale. Il float64 più vicino è 0,1000000000000000055511151231257827…, e 0.2 e 0.3 sono anch’essi leggermente fuori. Sommare i due valori arrotondati e arrotondare di nuovo finisce una unità accanto al float più vicino a 0.3, e per questo il confronto fallisce. Scrivi 0.1 qui sopra e guarda la riga «valore memorizzato esatto» per vedere direttamente l’arrotondamento.
Schemi speciali
- Zero: esponente e frazione tutti 0. Il bit di segno conta comunque, quindi esiste −0, che nei confronti è uguale a +0.
- Subnormale: esponente tutto 0, frazione diversa da zero. Sono valori minuscoli sotto l’intervallo normale, con precisione ridotta e senza 1 iniziale implicito.
- Infinito: esponente tutto 1, frazione 0. Nasce da overflow e divisione per zero.
- NaN: esponente tutto 1, frazione diversa da zero. Nasce da 0/0, ∞−∞, sqrt(−1). NaN non è uguale a nulla, nemmeno a sé stesso.
Leggere i bit a mano
- Dividi lo schema nei campi segno, esponente e frazione.
- Sottrai il bias dall’esponente per ottenere la potenza di due reale.
- Metti «1.» davanti ai bit della frazione (o «0.» per i subnormali) e moltiplica per 2 elevato a quella potenza.
Per 0x40490FDB: segno 0, esponente 10000000 = 128 → 128 − 127 = 1, frazione 1.1001001000011111101101 → 1,5707963 × 2 = 3,1415927, il float32 più vicino a π.
Domande
Qual è la differenza tra float e double?
Un float (float32) usa 32 bit e dà circa 7 cifre decimali significative; un double (float64) usa 64 bit e ne dà circa 16. I double sono il default nella maggior parte dei linguaggi (numeri di JavaScript, float di Python, double di C); i float si usano dove contano la memoria o la banda della GPU.
Perché l’esponente usa un bias invece del complemento a due?
Con un bias, i float dello stesso segno si confrontano correttamente come semplici interi: uno schema di bit più grande è un numero più grande. Questo rende ordinamento e confronto economici in hardware.
Cos’è l’epsilon di macchina?
La distanza tra 1,0 e il float rappresentabile successivo: 2⁻²³ ≈ 1,19 × 10⁻⁷ per float32 e 2⁻⁵² ≈ 2,22 × 10⁻¹⁶ per float64. La distanza cresce con la grandezza del numero: da 2²⁴ in su un float32 non può più rappresentare interi dispari.
Qual è l’intero più grande che un double memorizza esattamente?
2⁵³ = 9.007.199.254.740.992. Oltre, non tutti gli interi consecutivi sono rappresentabili, per questo JavaScript ha Number.MAX_SAFE_INTEGER (2⁵³ − 1) e BigInt.
Perché il mio linguaggio stampa 0.30000000000000004?
Perché è il decimale più corto che identifica in modo univoco il double risultante da 0.1 + 0.2. L’output è corretto; è il valore memorizzato a differire da 0.3.