L’aritmetica a cui siamo abituati ci dice che 0.1 + 0.2 = 0.3, ma cercando di eseguire in Javascript questa operazione tra decimali potremo verificare che:
console.log(0.1 + 0.2) // 0.30000000000000004
Questo non è un bug di Javascript ma è più correlato a come i float numbers vengono gestiti dai computer in generale [1].
Di seguito vi spiegherò quindi come ottenere risultati precisi quando lavoriamo con i decimali.
Nativamente i computer possono immagazzinare solo numeri interi (0 e 1) e per questo la rappresentazione dei numeri decimali non sempre è accurata.
L’aritmetica che ci è stata insegnata a scuola è in base 10 e possiamo verificare come sia possibile esprimere frazioni esatte di una unità solamente per denominatori che sono fattori primi della base (2, 5 e multipli) se non vogliamo incorrere in decimali periodici e un relativo “arrotondamento”, come dimostrano gli esempi seguenti:
- 1/2 = 0.5
- 1/3 = 0.333333333…
- 1/4 = 0.25
- 1/5 = 0.2
- 1/6 = 0.166666666…
- 1/7 = 0.142857143…
- 1/8 = 0.125
- 1/9 = 0.1111111111…
- 1/10 = 0.1
I computer come sappiamo non lavorano in base 10 ma in base 2 (sistema binario) e quindi sono in grado di esprimere frazioni esatte solo dividendo per il fattore primo 2. Qualsiasi altra frazione sarà soggetta ad un risultato con delle periodicità.
Pertanto l’unico float number rappresentabile accuratamente in base 2 è 1/2 (0.5).
Questo implica che un numero decimale esprimibile accuratamente in base 10 (es: 0.25, corrispondente a 1/4) possa non essere espresso con altrettanta precisione in base 2 [2].
Quando si eseguono operazioni matematiche su questi decimali l’approssimazione verrà tradotta in base 10 in modo più o meno preciso a seconda del linguaggio e delle eventuali librerie utilizzate [3].
Conclusioni
Per essere certi di ottenere un risultato accurato quando lavoriamo in Javascript con numeri decimali (ma anche in altri linguaggi) è consigliabile affidarsi a librerie specializzate che si sono già occupate di gestire questo problema in maniera consistente.
Alcune di queste innumerevoli librerie sono currency.js per quanto riguarda le operazioni finanziarie e math.js per applicazioni più generiche.
Attenzione! Alcune librerie, come l’appena citata math.js, si appoggiano comunque al Number type di Javascript per cui devono essere istruite sulla precisione che vogliamo nella formattazione in output per non incorrere nello stesso problema:
// prevent round-off errors showing up in output
const ans = math.add(0.1, 0.2) // 0.30000000000000004
math.format(ans, {precision: 14}) // '0.3'