# 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.

- Autore / Author: Diego Betto (https://diegobetto.com/about/)
- Pubblicato / Published: 2026-09-19
- URL: https://diegobetto.com/javascript-0-1-plus-0-2/
- Tag: javascript, numeri, floating point, ieee754

Apri una qualsiasi console JavaScript e scrivi questo:

```js
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`.

```js
(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:

```js
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.

```js
// 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:

```js
// 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.

> `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**

- [IEEE 754 – Wikipedia](https://it.wikipedia.org/wiki/IEEE_754)
- [Number.EPSILON – MDN](https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON)
- [0.30000000000000004.com — lo stesso problema in ogni linguaggio](https://0.30000000000000004.com/)
