Eseguendo calcoli matematici con numeri float in Javascript non sempre si ottengono risultati precisi. Il motivo sta nello standard usato per i calcoli, l’IEEE 754.

Alcuni esempi di quello che può capitare sono:

  • 3.3 – 1.1 = 2.1999999999999997;
  • 0.1 + 0.2 = 0.30000000000000004;
  • 0.05 * 0.35 = 0.017499999999999998.

Se, ad esempio, implementassimo un carrello della spesa ed avessimo la necessità di far eseguire al browser un calcolo monetario, così da ottenere un’anteprima della cifra da registrare nel database, non è il risultato ideale.

Come ottenere risultati precisi

Classe Big Number

La soluzione standard sta nell’impiego della classe Big Number, che prevede una serie di metodi per svolgere in modo preciso operazioni su numeri a virgola mobile. Personalmente ho trovato più comodo usare altri stratagemmi per ottenere risultati precisi coi float, senza caricare un altro script.

Metodo .toFixed();

Per arrotondare un numero a virgola mobile, tenendo solo un determinato numero di cifre decimali, Javascript prevede il metodo .toFixed(x), dove x è la lunghezza della parte decimale da preservare. Con questo metodo possiamo eliminare le cifre decimali inutili, arrotondando l’ultima utile con lo stesso criterio usato da Math.round() per i numeri interi.

Riprendiamo gli esempi di cui sopra:

  • (3.3 – 1.1).toFixed(1) == 2.2
  • (0.1 + 0.2).toFixed(1) == 0.3
  • (0.05 * 0.35).toFixed(4) == 0.175

Con .toFixed() otteniamo i risultati sperati, eliminando la parte decimale periodica che non serve e ottenendo l’arrotondamento per eccesso dell’ultima cifra decimale utile. Tuttavia, se ad esempio dovessimo fare 3.3 – 1.1 – 1.1 sarebbe ottimale prima arrotondare 3.3 – 1.1, poi, al numero ottenuto, si sottrae 1.1 e, sul risultato, si fa .toFixed(1).

Questo metodo, però, presenta due limiti:

  • restituisce una stringa, quindi attenzione a usare l’operatore +;
  • fissa un numero di decimali prestabilito; nel caso di una moltiplicazione, come quella dell’esempio, non sempre è possibile sapere quanto è lunga la parte decimale utile. Dovremmo inventarci un modo per eliminare quella inutile.

Dai decimali agli interi, dall’intero al decimale

Dato che il problema della precisione sussiste solo con i float, trasformiamo tutti i numeri coinvolti nell’operazione in interi, i quali ci daranno un risultato intero, da trasformare poi in decimale.
Vediamo alcuni esempi.

Somma e sottrazione

Prendiamo il primo caso, l’operazione 3.3 – 1.1. Javascript restituisce 2.1999999999999997, mentre noi vogliamo ottenere 2.2.
Per fare questo ogni sottraendo deve essere moltiplicato per 10 elevato al numero di cifre decimali. Dato che entrambi i numeri devono essere moltiplicati per lo stesso valore, se le cifre decimali sono in quantità differenti si considera quella maggiore tra i numeri. Nel caso specifico entrambi i sottraendi ne hanno una, quindi basta moltiplicarli per 101. Per essere più sicuri è bene arrotondare a intero ciascun numero.

Eseguiamo la sottrazione e, il risultato ottenuto, si divide per lo stesso valore per cui sono stati moltiplicati i sottraendi.
Quindi il codice è il seguente

n1=Math.round(n1*10); //101
n2=Math.round(n2*10); //101
var diff=n1-n2;
diff/=10;  //101

Moltiplicazione

Nel terzo esempio 0.05 viene moltiplicato per 0.35, ottenendo, come risultato, 0.017499999999999998, anziché 0.0175.
Qui il discorso si complica un po’. A differenza che nell’addizione e nella sottrazione, non è necessario moltiplicare i fattori per lo stesso numero; volendo, se ci fosse un numero con una cifra decimale e l’altro con due, si potrebbe moltiplicarli, rispettivamente, per 101 e 102.
Non è il nostro caso, visto che entrambi i fattori hanno 2 cifre decimali, perciò li moltiplicheremo per 102.

Il risultato ottenuto, tuttavia, rimane moltiplicato per 104, perché si sommano gli esponenti delle due elevazioni di 10. Pertanto il risultato deve essere diviso per 104. Se il primo fattore fosse stato moltiplicato per 101 e il secondo per 102, il risultato l’avremmo dovuto dividere per 103.

Questo è il codice

var n1=0.05;
var n2=0.35;
n1=Math.round(n1*100); //102
n2=Math.round(n2*100); //102
var prod=n1*n2;
prod/=10000; //104 (102 + 102)

Divisioni

Con la divisione ci si comporta come con le addizioni e le sottrazioni, cioè si moltiplicano dividendo e divisore per lo stesso numero, quindi 10 elevato al maggior numero di cifre decimali tra i due membri, con la differenza che il risultato è già esatto, dato che nelle divisioni 1/1 == 10/10.

var n1=2;
var n2=0.5;
n1=Math.round(n1*10);
n2=Math.round(n2*10);
var prod=n1/n2;

In questo caso forse non è necessario usare questa procedura, ma qui viene impiegata solo a scopo illustrativo.

Conclusione

La classe Big Number è molto valida per compiere operazioni con i float; tuttavia si tratta sempre di uno script da far caricare al browser e di una serie di dati da memorizzare in più.

Talvolta può essere utile .toFixed(), sopratutto se abbiamo l’esigenza di avere un numero determinato di cifre decimali.

L’ultimo metodo, che trovo più efficace rispetto a .toFixed(), è utile ma richiede un po’ di ragionamento.

Quale utilizzare dei tre? Non esiste, in assoluto, un metodo migliore o peggiore, dipende tutto da come i numeri float vengono impiegati nel nostro progetto. La sfida è sempre quella di scrivere del codice più performante ed elegante possibile.