Come gestire la precisione dei numeri in virgola mobile in JavaScript?


620

Ho il seguente script di test fittizio:

function test() {
  var x = 0.1 * 0.2;
  document.write(x);
}
test();

Questo stamperà il risultato 0.020000000000000004mentre dovrebbe solo stampare0.02 (se si utilizza la calcolatrice). Per quanto ho capito, ciò è dovuto a errori nella precisione della moltiplicazione in virgola mobile.

Qualcuno ha una buona soluzione in modo che in tal caso ottenga il risultato corretto 0.02? So che ci sono funzioni cometoFixed o arrotondare sarebbe un'altra possibilità, ma mi piacerebbe davvero stampare l'intero numero senza tagliare e arrotondare. Volevo solo sapere se uno di voi ha una soluzione bella ed elegante.

Certo, altrimenti arrotonderò a circa 10 cifre o giù di lì.


118
In realtà, l'errore è dovuto al fatto che non è possibile mappare 0.1un numero a virgola mobile binario finito.
Aaron Digulla,

10
La maggior parte delle frazioni non può essere convertita in decimale con precisione esatta. Una buona spiegazione è qui: docs.python.org/release/2.5.1/tut/node16.html
Nate Zaugg,

7
possibile duplicato di Math di JavaScript è rotto?
epascarello,

53
@SalmanA: Che il tuo runtime JavaScript ti nasconda questo problema non significa che mi sbagli.
Aaron Digulla,

5
Non sono d'accordo con Aaron, ci sono modi per codificare 0.1 perfettamente e completamente in binario. Ma IEEE 754 non lo definisce necessariamente. Immagina una rappresentazione in cui codificheresti la parte intera in binario da un lato, la parte decimale dall'altro, fino a n decimali, anche in binario, come un intero normale> 0, e infine la posizione del punto decimale . Bene, rappresenteresti 0,1 perfettamente, senza errori. A proposito, poiché JS utilizza un numero finito di decimali internamente, gli sviluppatori potrebbero anche codificare l'intestino per non commettere quell'errore sugli ultimi decimali.
Fabien Haddadi,

Risposte:


469

Dalla guida in virgola mobile :

Cosa posso fare per evitare questo problema?

Dipende dal tipo di calcoli che stai facendo.

  • Se hai davvero bisogno che i tuoi risultati si sommino esattamente, specialmente quando lavori con i soldi: usa un tipo di dati decimale speciale.
  • Se semplicemente non vuoi vedere tutte quelle posizioni decimali extra: formatta semplicemente il risultato arrotondato a un numero fisso di posizioni decimali quando lo visualizzi.
  • Se non si dispone di un tipo di dati decimale disponibile, un'alternativa è quella di lavorare con numeri interi, ad esempio fare calcoli di denaro interamente in centesimi. Ma questo è più lavoro e presenta alcuni svantaggi.

Si noti che il primo punto si applica solo se è necessario un comportamento decimale preciso specifico . La maggior parte delle persone non ne ha bisogno, sono solo irritati dal fatto che i loro programmi non funzionino correttamente con numeri come 1/10 senza rendersi conto che non batterebbero nemmeno lo stesso errore se si verificasse con 1/3.

Se il primo punto si applica davvero a te, usa BigDecimal per JavaScript , che non è affatto elegante, ma risolve effettivamente il problema piuttosto che fornire una soluzione imperfetta.


11
Ho notato il tuo link morto per BigDecimal e mentre cercavo un mirror, ho trovato un'alternativa chiamata BigNumber: jsfromhell.com/classes/bignumber
Jacksonkr

4
@ bass-t: Sì, ma i float possono rappresentare esattamente numeri interi fino alla lunghezza del significato, e secondo lo standard ECMA è un float a 64 bit. Quindi può rappresentare esattamente numeri interi fino a 2 ^ 52
Michael Borgwardt,

5
@Karl: la frazione decimale 1/10 non può essere rappresentata come una frazione binaria finita nella base 2, ed è quello che sono i numeri Javascript. Quindi è esattamente lo stesso problema.
Michael Borgwardt

12
Ho imparato oggi che anche i numeri interi hanno problemi di precisione in JavaScript. Considera che console.log(9332654729891549)effettivamente stampa 9332654729891548(cioè fuori da uno!)
mlat

12
@mlathe: Doh .. ;P... Tra 2⁵²= 4,503,599,627,370,496e 2⁵³= 9,007,199,254,740,992i numeri rappresentabili sono esattamente i numeri interi . Per l'intervallo successivo, da 2⁵³a 2⁵⁴, tutto viene moltiplicato per2 , quindi i numeri rappresentabili sono quelli pari , ecc. Al contrario, per l'intervallo precedente da 2⁵¹a 2⁵², la spaziatura è0.5 , ecc. Ciò è dovuto semplicemente al fatto di aumentare | diminuire la base | radix 2 | esponente binario in / del valore float a 64 bit (che a sua volta spiega il comportamento 'imprevisto' raramente documentato di toPrecision()per valori tra 0e 1).
GitaarLAB

126

Mi piace la soluzione di Pedro Ladaria e uso qualcosa di simile.

function strip(number) {
    return (parseFloat(number).toPrecision(12));
}

A differenza della soluzione Pedros, questo arrotonderà per eccesso 0,999 ... ripetendo ed è preciso a più / meno uno sulla cifra meno significativa.

Nota: per gestire i float a 32 o 64 bit, utilizzare toPrecision (7) e toPrecision (15) per risultati ottimali. Vedi questa domanda per informazioni sul perché.


21
Qualche motivo per cui hai scelto 12?
Qwertymk,

18
toPrecisionrestituisce una stringa anziché un numero. Questo potrebbe non essere sempre desiderabile.
SStanley,

7
parseFloat (1.005) .toPrecision (3) => 1.00
Peter,

5
@utente2428118, lo so, intendevo mostrare l'errore di arrotondamento, il risultato è 1.00 invece di 1.01
Peter

9
Quello che ha detto @ user2428118 potrebbe non essere abbastanza ovvio: (9.99*5).toPrecision(2)= 50 invece di 49.95 perché toPrecision conta l'intero numero, non solo i decimali. Puoi quindi usare toPrecision(4), ma se il tuo risultato è> 100, sei di nuovo sfortunato, perché consentirà i primi tre numeri e un decimale, in questo modo spostando il punto e rendendolo più o meno inutilizzabile. Ho finito per usare toFixed(2)invece
aexl il

79

Per i matematicamente inclini: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

L'approccio raccomandato è di utilizzare i fattori di correzione (moltiplicare per una potenza adeguata di 10 in modo che l'aritmetica avvenga tra numeri interi). Ad esempio, nel caso di 0.1 * 0.2, il fattore di correzione è 10e si sta eseguendo il calcolo:

> var x = 0.1
> var y = 0.2
> var cf = 10
> x * y
0.020000000000000004
> (x * cf) * (y * cf) / (cf * cf)
0.02

Una soluzione (molto rapida) è simile a:

var _cf = (function() {
  function _shift(x) {
    var parts = x.toString().split('.');
    return (parts.length < 2) ? 1 : Math.pow(10, parts[1].length);
  }
  return function() { 
    return Array.prototype.reduce.call(arguments, function (prev, next) { return prev === undefined || next === undefined ? undefined : Math.max(prev, _shift (next)); }, -Infinity);
  };
})();

Math.a = function () {
  var f = _cf.apply(null, arguments); if(f === undefined) return undefined;
  function cb(x, y, i, o) { return x + f * y; }
  return Array.prototype.reduce.call(arguments, cb, 0) / f;
};

Math.s = function (l,r) { var f = _cf(l,r); return (l * f - r * f) / f; };

Math.m = function () {
  var f = _cf.apply(null, arguments);
  function cb(x, y, i, o) { return (x*f) * (y*f) / (f * f); }
  return Array.prototype.reduce.call(arguments, cb, 1);
};

Math.d = function (l,r) { var f = _cf(l,r); return (l * f) / (r * f); };

In questo caso:

> Math.m(0.1, 0.2)
0.02

Consiglio vivamente di utilizzare una libreria testata come SinfulJS


1
Adoro questa soluzione alternativa elegante ma sembra non essere perfetta: jsfiddle.net/Dm6F5/1 Math.a (76.65, 38.45) restituisce 115.1000000000000002
nicolallias

3
Math.m (10,2332226616) mi sta dando "-19627406800" che è un valore negativo ... Spero che ci debba essere un limite superiore - potrebbe essere che sta causando questo problema. Per favore, suggerisci
Shiva Komuravelly il

1
Tutto questo sembra grandioso, ma sembra avere qualche errore lì dentro da qualche parte.
MrYellow

5
Soluzione molto rapida ha detto ... correzione rotta che nessuno ha mai detto.
Cozzbie,

2
Non utilizzare il codice sopra. Non è assolutamente una "soluzione rapida" se non funziona. Questa è una domanda di matematica, quindi è richiesta precisione.
Drenai,

49

Stai solo eseguendo la moltiplicazione? In tal caso, puoi usare a tuo vantaggio un segreto sull'aritmetica decimale. Questo è quello NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals. Vale a dire che se abbiamo 0.123 * 0.12allora sappiamo che ci saranno 5 decimali perché 0.123ha 3 decimali e ne 0.12ha due. Pertanto, se JavaScript ci ha fornito un numero come quello 0.014760000002che possiamo tranquillamente arrotondare al 5 ° decimale senza paura di perdere precisione.


6
... e come ottenere la quantità esatta di cifre decimali.
line-o

7
0,5 * 0,2 = 0,10; Puoi comunque troncare con 2 decimali (o meno). Ma non ci sarà mai un numero con alcun significato matematico oltre questa legge.
Nate Zaugg,

3
Hai una citazione per questo? Si noti inoltre che lo stesso non vale per la divisione.
Griffin,

3
@NateZaugg non puoi troncare i decimali straripanti, devi arrotondare l'importo, perché 2090.5 * 8.61 è 17999.205 ma in float è 17999.20499999999998
Lostfields

3
@Lostfields - Hai ragione! Ho aggiornato la mia risposta.
Nate Zaugg,

29

Stai cercando sprintfun'implementazione per JavaScript, in modo da poter scrivere float con piccoli errori (poiché sono memorizzati in formato binario) in un formato che ti aspetti.

Prova javascript-sprintf , lo chiameresti così:

var yourString = sprintf("%.2f", yourNumber);

per stampare il tuo numero come float con due cifre decimali.

Puoi anche usare Number.toFixed () per scopi di visualizzazione, se preferisci non includere più file semplicemente per arrotondare in virgola mobile con una data precisione.


4
Penso che questa sia la soluzione più pulita. A meno che non sia davvero necessario che il risultato sia 0,02, l'errore piccolo è trascurabile. Sembra che l'importante sia che il tuo numero sia visualizzato correttamente, non che tu abbia una precisione arbitraria.
Long Ouyang,

2
Per visualizzare questa è davvero l'opzione migliore, per calcoli complicati, controlla la risposta di Borgwardt.
Non disponibile dal

4
Ma di nuovo questo restituirà esattamente la stessa stringa di yourNumber.toFixed (2).
Robert,

27

Sto trovando BigNumber.js che soddisfa le mie esigenze.

Una libreria JavaScript per l'aritmetica decimale e non decimale di precisione arbitraria.

Ha una buona documentazione e l'autore è molto diligente nel rispondere al feedback.

Lo stesso autore ha altre 2 librerie simili:

Big.js

Una libreria JavaScript piccola e veloce per l'aritmetica decimale con precisione arbitraria. La sorellina di bignumber.js.

e Decimal.js

Un tipo decimale di precisione arbitraria per JavaScript.

Ecco un po 'di codice usando BigNumber:

$(function(){

  
  var product = BigNumber(.1).times(.2);  
  $('#product').text(product);

  var sum = BigNumber(.1).plus(.2);  
  $('#sum').text(sum);


});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<!-- 1.4.1 is not the current version, but works for this example. -->
<script src="http://cdn.bootcss.com/bignumber.js/1.4.1/bignumber.min.js"></script>

.1 &times; .2 = <span id="product"></span><br>
.1 &plus; .2 = <span id="sum"></span><br>


3
L'utilizzo di una biblioteca è sicuramente la scelta migliore secondo me.
Anthony,

1
Da questo link github.com/MikeMcl/big.js/issues/45 bignumber.js -> financial decimal.js -> scientific big.js -> ???
Vee

20
var times = function (a, b) {
    return Math.round((a * b) * 100)/100;
};

---o---

var fpFix = function (n) {
    return Math.round(n * 100)/100;
};

fpFix(0.1*0.2); // -> 0.02

---anche---

var fpArithmetic = function (op, x, y) {
    var n = {
            '*': x * y,
            '-': x - y,
            '+': x + y,
            '/': x / y
        }[op];        

    return Math.round(n * 100)/100;
};

--- come in ---

fpArithmetic('*', 0.1, 0.2);
// 0.02

fpArithmetic('+', 0.1, 0.2);
// 0.3

fpArithmetic('-', 0.1, 0.2);
// -0.1

fpArithmetic('/', 0.2, 0.1);
// 2

4
Penso che darebbe lo stesso problema di conseguenza. Restituisci un virgola mobile, quindi una grande possibilità anche il valore restituito sarà "errato".
Gertjan,

1
Molto intelligente e utile, +1.
Jonatas Walker,

18

Questa funzione determinerà la precisione necessaria dalla moltiplicazione di due numeri in virgola mobile e restituirà un risultato con la precisione appropriata. Elegante anche se non lo è.

function multFloats(a,b){
  var atens = Math.pow(10,String(a).length - String(a).indexOf('.') - 1), 
      btens = Math.pow(10,String(b).length - String(b).indexOf('.') - 1); 
  return (a * atens) * (b * btens) / (atens * btens); 
}

Ew. Sì, convertiamo i numeri in stringhe per la matematica in virgola mobile e proponiamo anche questa come risposta.
Andrew

17

Sorprendentemente, questa funzione non è stata ancora pubblicata, sebbene altre ne abbiano varianti simili. Viene dai documenti web MDN per Math.round (). È conciso e consente una precisione variabile.

function precisionRound(number, precision) {
  var factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}

console.log (precisionRound (1234.5678, 1)); // output atteso: 1234.6

console.log (precisionRound (1234.5678, -1)); // output previsto: 1230

var inp = document.querySelectorAll('input');
var btn = document.querySelector('button');

btn.onclick = function(){
  inp[2].value = precisionRound( parseFloat(inp[0].value) * parseFloat(inp[1].value) , 5 );
};

//MDN function
function precisionRound(number, precision) {
  var factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}
button{
display: block;
}
<input type='text' value='0.1'>
<input type='text' value='0.2'>
<button>Get Product</button>
<input type='text'>

AGGIORNAMENTO: 20 agosto 2019 Ho appena notato questo errore. Credo che sia dovuto a un errore di precisione in virgola mobile con Math.round ().

precisionRound(1.005, 2) // produces 1, incorrect, should be 1.01

Queste condizioni funzionano correttamente:

precisionRound(0.005, 2) // produces 0.01
precisionRound(1.0005, 3) // produces 1.001
precisionRound(1234.5, 0) // produces 1235
precisionRound(1234.5, -1) // produces 1230

fix:

function precisionRoundMod(number, precision) {
  var factor = Math.pow(10, precision);
  var n = precision < 0 ? number : 0.01 / factor + number;
  return Math.round( n * factor) / factor;
}

Questo aggiunge solo una cifra a destra quando si arrotondano i decimali. MDN ha aggiornato la pagina Math.round, quindi forse qualcuno potrebbe fornire una soluzione migliore.


risposta sbagliata. 10.2 restituirà sempre 10.19. jsbin.com/tozogiwide/edit?html,js,console,output
Žilvinas

@ Žilvinas Il link JSBin che hai pubblicato non utilizza la funzione MDN sopra elencata. Penso che il tuo commento sia diretto alla persona sbagliata.
HelloWorldPeace,

13

Devi solo prendere una decisione su quante cifre decimali vuoi effettivamente - non puoi avere la torta e mangiarla anche tu :-)

Gli errori numerici si accumulano con ogni ulteriore operazione e se non lo si interrompe in anticipo crescerà. Le librerie numeriche che presentano risultati apparentemente puliti semplicemente tagliano le ultime 2 cifre ad ogni passo, i coprocessori numerici hanno anche una lunghezza "normale" e "completa" per lo stesso motivo. I cuf-off sono economici per un processore ma molto costosi per te in uno script (moltiplicando e dividendo e usando pov (...)). Una buona libreria matematica fornirebbe floor (x, n) per eseguire il cut-off per te.

Quindi almeno dovresti fare var / costante globale con pov (10, n) - il che significa che hai deciso la precisione di cui hai bisogno :-) Quindi:

Math.floor(x*PREC_LIM)/PREC_LIM  // floor - you are cutting off, not rounding

Potresti anche continuare a fare matematica e tagliare solo alla fine - supponendo che stai solo mostrando e non facendo if-s con risultati. Se riesci a farlo, allora .toFixed (...) potrebbe essere più efficiente.

Se stai facendo if-s / confronti e non vuoi tagliare, allora hai anche bisogno di una piccola costante, di solito chiamata eps, che è una cifra decimale superiore all'errore massimo previsto. Supponi che il tuo cut-off sia degli ultimi due decimali - quindi il tuo eps ha 1 al 3 ° posto dall'ultimo (3 ° meno significativo) e puoi usarlo per confrontare se il risultato è all'interno dell'intervallo eps del previsto (0,02 -eps <0,1 * 0,2 <0,02 + eps).


Puoi anche aggiungere 0,5 per fare l'arrotondamento di un povero: Math.floor (x * PREC_LIM + 0.5) / PREC_LIM
cmroanirgo

Nota però, che ad esempio Math.floor(-2.1)è -3. Quindi forse usa ad esMath[x<0?'ceil':'floor'](x*PREC_LIM)/PREC_LIM
MikeM il

Perché floorinvece di round?
Quinn Comendant,

12

È possibile utilizzare parseFloat()e toFixed()se si desidera aggirare questo problema per una piccola operazione:

a = 0.1;
b = 0.2;

a + b = 0.30000000000000004;

c = parseFloat((a+b).toFixed(2));

c = 0.3;

a = 0.3;
b = 0.2;

a - b = 0.09999999999999998;

c = parseFloat((a-b).toFixed(2));

c = 0.1;

11

La funzione round () su phpjs.org funziona bene: http://phpjs.org/functions/round

num = .01 + .06;  // yields 0.0699999999999
rnum = round(num,12); // yields 0.07

2
@jrg Per convenzione, i numeri che terminano con un "5" sono arrotondati al punto più vicino (perché l'arrotondamento sempre su o giù introdurrebbe un errore nei risultati). Pertanto, 4.725 arrotondato al secondo decimale dovrebbe effettivamente essere 4,72.
Mark A. Durham,

9

0.6 * 3 è fantastico!)) Per me funziona benissimo:

function dec( num )
{
    var p = 100;
    return Math.round( num * p ) / p;
}

Molto molto semplice))


Funzionerebbe comunque con qualcosa del genere 8.22e-8 * 1.3?
Paul Carlton,

0.6 x 3 = 1.8, il codice che dai risultati a 2 ... quindi non va bene.
Zyo

@Zyo Restituisce 1.8 in questo caso. Come lo hai eseguito?
Drenai,

Interessante. In questo puoi scambiare gli operatori di moltiplicazione e divisione e funziona anche.
Andrew

9

Si noti che per l'uso generale, questo comportamento è probabilmente accettabile.
Il problema sorge quando si confrontano quei valori in virgola mobile per determinare un'azione appropriata.
Con l'avvento di ES6, Number.EPSILONviene definita una nuova costante per determinare il margine di errore accettabile:
quindi invece di eseguire il confronto in questo modo

0.1 + 0.2 === 0.3 // which returns false

puoi definire una funzione di confronto personalizzata, in questo modo:

function epsEqu(x, y) {
    return Math.abs(x - y) < Number.EPSILON;
}
console.log(epsEqu(0.1+0.2, 0.3)); // true

Fonte: http://2ality.com/2015/04/numbers-math-es6.html#numberepsilon


Nel mio caso Number.EPSILON era troppo piccolo, il che ha comportato, ad esempio0.9 !== 0.8999999761581421
Tom

8

Il risultato ottenuto è corretto e abbastanza coerente tra le implementazioni in virgola mobile in linguaggi, processori e sistemi operativi diversi: l'unica cosa che cambia è il livello di imprecisione quando il float è in realtà un doppio (o superiore).

0,1 in virgola mobile binaria è come 1/3 in decimale (ovvero 0,3333333333333 ... per sempre), non esiste un modo preciso per gestirlo.

Se hai a che fare con i float ti aspetti sempre piccoli errori di arrotondamento, quindi dovrai sempre arrotondare il risultato visualizzato a qualcosa di ragionevole. In cambio ottieni aritmetica molto molto veloce e potente perché tutti i calcoli sono nel binario nativo del processore.

Il più delle volte la soluzione non è quella di passare all'aritmetica in virgola fissa, principalmente perché è molto più lenta e il 99% delle volte non è necessaria la precisione. Se hai a che fare con cose che richiedono quel livello di accuratezza (ad esempio le transazioni finanziarie) Javascript probabilmente non è lo strumento migliore da usare comunque (poiché vuoi imporre i tipi a virgola fissa un linguaggio statico è probabilmente migliore ).

Stai cercando la soluzione elegante, quindi temo che sia così: i float sono veloci ma hanno piccoli errori di arrotondamento - sempre arrotondati a qualcosa di sensato quando si visualizzano i loro risultati.


8

Per evitare ciò, dovresti lavorare con valori interi invece che in virgola mobile. Pertanto, quando si desidera avere 2 posizioni di precisione, lavorare con i valori * 100, per 3 posizioni utilizzare 1000. Durante la visualizzazione, utilizzare un formattatore per inserire il separatore.

Molti sistemi omettono di lavorare con i decimali in questo modo. Questo è il motivo per cui molti sistemi funzionano con centesimi (come numeri interi) anziché dollari / euro (come virgola mobile).


7

Problema

Il virgola mobile non può memorizzare esattamente tutti i valori decimali. Pertanto, quando si utilizzano formati in virgola mobile, ci saranno sempre errori di arrotondamento sui valori di input. Gli errori sugli input ovviamente comportano errori sull'output. Nel caso di una funzione o di un operatore discreto, ci possono essere grandi differenze nell'output attorno al punto in cui la funzione o l'operatore è discreto.

Input e output per valori in virgola mobile

Pertanto, quando si utilizzano variabili in virgola mobile, è necessario essere sempre consapevoli di questo. E qualunque sia l'output desiderato da un calcolo con virgola mobile dovrebbe sempre essere formattato / condizionato prima di essere visualizzato tenendo presente questo.
Quando vengono utilizzate solo funzioni e operatori continui, spesso eseguirà l'arrotondamento alla precisione desiderata (non troncare). Le funzionalità di formattazione standard utilizzate per convertire i float in stringa di solito lo fanno per te.
Poiché l'arrotondamento aggiunge un errore che può far sì che l'errore totale sia più della metà della precisione desiderata, l'output deve essere corretto in base alla precisione attesa degli input e alla precisione desiderata dell'output. Dovresti

  • Arrotondare gli input alla precisione prevista o assicurarsi che nessun valore possa essere inserito con maggiore precisione.
  • Aggiungere un valore piccolo agli output prima di arrotondarli / formattarli che sia inferiore o uguale a 1/4 della precisione desiderata e maggiore dell'errore massimo previsto causato dagli errori di arrotondamento sull'input e durante il calcolo. Se ciò non è possibile, la combinazione della precisione del tipo di dati utilizzato non è sufficiente per fornire la precisione di output desiderata per il calcolo.

Queste 2 cose di solito non vengono fatte e nella maggior parte dei casi le differenze causate dal non farle sono troppo piccole per essere importanti per la maggior parte degli utenti, ma avevo già un progetto in cui l'output non veniva accettato dagli utenti senza quelle correzioni.

Funzioni o operatori discreti (come il modulo)

Quando sono coinvolti operatori o funzioni discreti, potrebbero essere necessarie ulteriori correzioni per assicurarsi che l'output sia come previsto. L'arrotondamento e l'aggiunta di piccole correzioni prima dell'arrotondamento non possono risolvere il problema.
Potrebbe essere necessario un controllo / correzione speciale sui risultati di calcolo intermedi, immediatamente dopo l'applicazione della funzione o dell'operatore discreti. Per un caso specifico (operatore del modulo ), vedere la mia risposta alla domanda: Perché l'operatore del modulo restituisce il numero frazionario in javascript?

Meglio evitare di avere il problema

Spesso è più efficiente evitare questi problemi utilizzando tipi di dati (formati a numero intero o fisso) per calcoli come questo che possono memorizzare l'input previsto senza errori di arrotondamento. Un esempio è che non si dovrebbero mai usare valori in virgola mobile per i calcoli finanziari.


4

Dai un'occhiata all'aritmetica a virgola fissa . Probabilmente risolverà il tuo problema, se l'intervallo di numeri su cui vuoi operare è piccolo (es. Valuta). Lo arrotonderei ad alcuni valori decimali, che è la soluzione più semplice.


5
Il problema non è in virgola mobile rispetto a punto fisso, il problema è binario vs. decimale.
Michael Borgwardt,

4

Prova la mia biblioteca aritmetica chiliadica, che puoi vedere qui . Se vuoi una versione successiva, posso procurartene una.


4

Non è possibile rappresentare la maggior parte delle frazioni decimali esattamente con i tipi binari in virgola mobile (che è ciò che ECMAScript utilizza per rappresentare i valori in virgola mobile). Quindi non esiste una soluzione elegante a meno che non si utilizzino tipi aritmetici di precisione arbitraria o un tipo a virgola mobile basato su decimali. Ad esempio, l'app Calcolatrice fornita con Windows ora utilizza l'aritmetica di precisione arbitraria per risolvere questo problema .


4

inserisci qui la descrizione dell'immagine

    You can use library https://github.com/MikeMcl/decimal.js/. 
    it will   help  lot to give proper solution. 
    javascript console output 95 *722228.630 /100 = 686117.1984999999
    decimal library implementation 
    var firstNumber = new Decimal(95);
    var secondNumber = new Decimal(722228.630);
    var thirdNumber = new Decimal(100);
    var partialOutput = firstNumber.times(secondNumber);
    console.log(partialOutput);
    var output = new Decimal(partialOutput).div(thirdNumber);
    alert(output.valueOf());
    console.log(output.valueOf())== 686117.1985

3

Hai ragione, la ragione di ciò è la precisione limitata dei numeri in virgola mobile. Memorizza i tuoi numeri razionali come una divisione di due numeri interi e nella maggior parte dei casi sarai in grado di memorizzare numeri senza alcuna perdita di precisione. Quando si tratta di stampare, è possibile che si desideri visualizzare il risultato come frazione. Con la rappresentazione che ho proposto, diventa banale.

Ovviamente ciò non aiuterà molto con i numeri irrazionali. Ma potresti voler ottimizzare i tuoi calcoli nel modo in cui causeranno il minimo problema (ad esempio rilevando situazioni come sqrt(3)^2).


Hai ragione, la ragione di ciò è la precisione limitata dei numeri in virgola mobile - in <pedant>realtà, l'OP lo ha messo giù per imprecise operazioni in virgola mobile, il che è sbagliato</pedant>
detly

3

Ho avuto un brutto problema di arrotondamento con la mod 3. A volte, quando avrei dovuto ottenere 0, avrei ricevuto .000 ... 01. È abbastanza facile da gestire, basta provare <= .01. Ma poi a volte ottenevo 2.99999999999998. AHIA!

BigNumbers ha risolto il problema, ma ha introdotto un altro problema, un po 'ironico. Quando ho provato a caricare 8.5 in BigNumbers mi è stato comunicato che erano davvero 8.4999 ... e che aveva più di 15 cifre significative. Ciò significava che BigNumbers non poteva accettarlo (credo di aver menzionato questo problema in qualche modo ironico).

Soluzione semplice al problema ironico:

x = Math.round(x*100);
// I only need 2 decimal places, if i needed 3 I would use 1,000, etc.
x = x / 100;
xB = new BigNumber(x);

2

Usa numero (1.234443) .toFisso (2); stamperà 1.23

function test(){
    var x = 0.1 * 0.2;
    document.write(Number(x).toFixed(2));
}
test();

2

decimal.js , big.js o bignumber.js possono essere utilizzati per evitare problemi di manipolazione in virgola mobile in Javascript:

0.1 * 0.2                                // 0.020000000000000004
x = new Decimal(0.1)
y = x.times(0.2)                          // '0.2'
x.times(0.2).equals(0.2)                  // true

big.js: minimalista; facile da usare; precisione specificata in decimali; precisione applicata solo alla divisione.

bignumber.js: basi 2-64; opzioni di configurazione; NaN; Infinito; precisione specificata in decimali; precisione applicata solo alla divisione; prefissi di base.

decimal.js: basi 2-64; opzioni di configurazione; NaN; Infinito; potenze non intere, exp, ln, log; precisione specificata in cifre significative; precisione sempre applicata; numeri casuali.

collegamento a confronti dettagliati


2

Elegante, prevedibile e riutilizzabile

Affrontiamo il problema in modo elegante riutilizzabile. Le seguenti sette righe ti permetteranno di accedere alla precisione in virgola mobile che desideri su qualsiasi numero semplicemente aggiungendo .decimalalla fine del numero, della formula o della Mathfunzione integrata.

// First extend the native Number object to handle precision. This populates
// the functionality to all math operations.

Object.defineProperty(Number.prototype, "decimal", {
  get: function decimal() {
    Number.precision = "precision" in Number ? Number.precision : 3;
    var f = Math.pow(10, Number.precision);
    return Math.round( this * f ) / f;
  }
});


// Now lets see how it works by adjusting our global precision level and 
// checking our results.

console.log("'1/3 + 1/3 + 1/3 = 1' Right?");
console.log((0.3333 + 0.3333 + 0.3333).decimal == 1); // true

console.log(0.3333.decimal); // 0.333 - A raw 4 digit decimal, trimmed to 3...

Number.precision = 3;
console.log("Precision: 3");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0.001

Number.precision = 2;
console.log("Precision: 2");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 1;
console.log("Precision: 1");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 0;
console.log("Precision: 0");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0

Saluti!


2
Se si sceglie di effettuare il downgrade, fornire almeno un motivo.
Bernesto,

1

Uso

var x = 0.1*0.2;
 x =Math.round(x*Math.pow(10,2))/Math.pow(10,2);

4
Hmm ... ma nota, questo arrotonda sempre a 2 decimali. Questa sarebbe ovviamente un'opzione, ma per quanto riguarda il calcolo 0,55 * 0,55 (dal momento che non conosco in anticipo i numeri esatti. Ciò darebbe 0,3 anziché 0,3025. Naturalmente potrei quindi utilizzare Math.round(x*Math.pow(10,4))/Math.pow(10,4);. L'arrotondamento è sempre un'opzione, ma volevo solo sapere se esiste una soluzione migliore
Juri,



1

Questo funziona per me:

function round_up( value, precision ) { 
    var pow = Math.pow ( 10, precision ); 
    return ( Math.ceil ( pow * value ) + Math.ceil ( pow * value - Math.ceil ( pow * value ) ) ) / pow; 
}

round_up(341.536, 2); // 341.54

1
sfortunatamente, round_up (4.15,2) => 4.16.
jrg

1

Uscita utilizzando la seguente funzione:

var toFixedCurrency = function(num){
    var num = (num).toString();
    var one = new RegExp(/\.\d{1}$/).test(num);
    var two = new RegExp(/\.\d{2,}/).test(num);
    var result = null;

    if(one){ result = num.replace(/\.(\d{1})$/, '.$10');
    } else if(two){ result = num.replace(/\.(\d{2})\d*/, '.$1');
    } else { result = num*100; }

    return result;
}

function test(){
    var x = 0.1 * 0.2;
    document.write(toFixedCurrency(x));
}

test();

Prestare attenzione all'output toFixedCurrency(x).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.