
Perché 0.1 + 0.2 !== 0.3 in JavaScript (e in quasi ogni linguaggio)
Come funzionano davvero i numeri floating-point IEEE 754, perché non è un bug specifico di JavaScript, e i modi sicuri per confrontare e memorizzare numeri decimali.
Apri una qualsiasi console JavaScript e scrivi questo:
0.1 + 0.2 === 0.3; // false
0.1 + 0.2; // 0.30000000000000004
Sembra un bug. Non lo è — e non è nemmeno specifico di JavaScript. Python, Java, C, Go, Rust: ogni linguaggio che usa numeri floating-point a doppia precisione IEEE 754 standard produce esattamente lo stesso risultato. Capire perché trasforma questo da strano gotcha in un fatto prevedibile e gestibile su come i computer rappresentano i numeri.
Come funzionano davvero i double IEEE 754
Il tipo Number di JavaScript memorizza ogni numero — intero o decimale — come un float a 64 bit a doppia precisione IEEE 754. Quel formato rappresenta i numeri in binario, usando un numero fisso di bit per la parte frazionaria. Funziona perfettamente per numeri con una rappresentazione binaria pulita (0.5, 0.25, 2), ma molte frazioni decimali che sembrano “pulite” in base 10 non hanno un equivalente binario esatto — allo stesso modo in cui 1/3 non ha una rappresentazione finita esatta in base 10 (0.3333...).
0.1 in binario è una frazione che si ripete all’infinito, proprio come 1/3 in decimale. Siccome il formato ha solo 64 bit, viene arrotondato al valore rappresentabile più vicino — che è estremamente vicino a 0.1, ma non esattamente 0.1. Somma insieme due di questi minuscoli errori di arrotondamento (quello di 0.1 e quello di 0.2), e il risultato non si arrotonda più esattamente a 0.3.
(0.1).toFixed(20); // "0.10000000000000000555"
(0.2).toFixed(20); // "0.20000000000000001110"
Nessuno dei due valori è esattamente ciò che sembra — sono i double più vicini disponibili, e i minuscoli errori si accumulano.
Perché non è un bug da “correggere”
Non è un’ingegneria sciatta — è un trade-off intrinseco nel rappresentare numeri reali infiniti con un numero fisso di bit, presente praticamente in ogni linguaggio di programmazione mainstream nel suo tipo numerico di default. L’alternativa (aritmetica decimale a precisione arbitraria di default) sarebbe drammaticamente più lenta per la stragrande maggioranza del codice numerico che non ha bisogno di precisione decimale esatta.
Confrontare numeri floating-point in sicurezza
Non confrontare mai i float per uguaglianza esatta dopo che sono passati per dell’aritmetica. Invece, verifica se la differenza è più piccola di una soglia accettabilmente piccola:
function nearlyEqual(a, b, epsilon = Number.EPSILON) {
return Math.abs(a - b) < epsilon;
}
nearlyEqual(0.1 + 0.2, 0.3); // true
Number.EPSILON è la differenza più piccola che JavaScript può rappresentare tra 1 e il numero rappresentabile successivo — una tolleranza di default ragionevole per confronti vicino a quella grandezza, anche se per numeri lontani da 1 potrebbe servirti una tolleranza più grande e scalata.
Il denaro: il caso in cui conta davvero
L’errore di arrotondamento floating-point sopra è di solito invisibile nel codice quotidiano, ma diventa un problema reale nel momento in cui sommi prezzi, calcoli totali, o fai qualsiasi cosa che coinvolga valuta, dove gli utenti noteranno un centesimo mancante.
// Fragile — errore di arrotondamento accumulato su molte addizioni
let total = 0;
[19.99, 5.0, 3.5].forEach((price) => (total += price));
console.log(total); // 28.49 oggi, ma questa classe di bug peggiora molto su scala
La correzione standard è lavorare nella più piccola unità di valuta (centesimi, non euro) usando interi, che non hanno affatto questo problema di arrotondamento:
// Robusto — gli interi non hanno problemi di arrotondamento floating-point
const centesimi = [1999, 500, 350];
const totaleCentesimi = centesimi.reduce((sum, c) => sum + c, 0);
console.log(totaleCentesimi / 100); // 28.49, calcolato esattamente
Per qualsiasi cosa oltre a semplici somme — conversione multi-valuta, calcoli fiscali con molte cifre decimali — usa una libreria dedicata all’aritmetica decimale (come decimal.js o big.js) invece di scrivere a mano matematica basata sui centesimi ovunque.
💡 Consiglio
toFixed(2) da solo non risolve il problema — arrotonda solo la stringa visualizzata, il numero
sottostante porta ancora l’errore accumulato, che può ripresentarsi la prossima volta che fai
aritmetica su di esso. Correggi la rappresentazione (interi, o una libreria decimale), non solo la
formattazione.
FAQ
❓ Questo riguarda anche gli interi?
Non per l’intervallo che conta praticamente — gli interi fino a 2^53 (Number.MAX_SAFE_INTEGER)
sono rappresentati esattamente in un double. L’errore di arrotondamento sopra è specifico dei
valori decimali frazionari che non hanno una rappresentazione binaria esatta.
❓ BigInt può risolvere questo problema?
BigInt risolve un problema correlato ma diverso — interi a precisione arbitraria, non
decimali. Non ha affatto una parte frazionaria, quindi non è una correzione immediata per la
matematica del denaro, anche se rappresentare la valuta come centesimi interi (come mostrato
sopra) si abbina naturalmente ad esso per somme molto grandi.
❓ Temporal o altre nuove feature JS risolvono questo?
No — Temporal risolve la correttezza di data/ora, un problema separato dall’aritmetica decimale.
Esiste una proposal TC39 separata per un tipo Decimal nativo, ma al momento non è ancora
arrivata, quindi la matematica basata su interi o una libreria restano la correzione pratica.
Conclusione
0.1 + 0.2 !== 0.3 non è una stranezza di JavaScript — è una conseguenza diretta e prevedibile della rappresentazione delle frazioni decimali in floating point binario, condivisa da quasi ogni linguaggio mainstream. Non confrontare mai i float per uguaglianza esatta dopo dell’aritmetica; usa invece un confronto basato su epsilon, e per il denaro in particolare, lavora in centesimi interi o usa una libreria decimale invece di fidarti che toFixed() copra la rappresentazione sottostante.
Riferimenti

Co-Fondatore & CTO presso PAPION. Senior full-stack engineer specializzato in React, TypeScript, Node.js e sicurezza applicativa.