Wie gehe ich mit der Genauigkeit von Gleitkommazahlen in JavaScript um?

618

Ich habe das folgende Dummy-Testskript:

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

Dadurch wird das Ergebnis gedruckt, 0.020000000000000004während es nur gedruckt werden soll 0.02(wenn Sie Ihren Taschenrechner verwenden). Soweit ich verstanden habe, ist dies auf Fehler in der Gleitkomma-Multiplikationsgenauigkeit zurückzuführen.

Hat jemand eine gute Lösung, damit ich in diesem Fall das richtige Ergebnis erhalte 0.02? Ich weiß, dass es Funktionen wie toFixedoder Rundung gibt, die eine andere Möglichkeit wäre, aber ich möchte wirklich die ganze Zahl ohne Schneiden und Runden drucken lassen. Ich wollte nur wissen, ob einer von Ihnen eine schöne, elegante Lösung hat.

Ansonsten werde ich natürlich auf ungefähr 10 Stellen runden.

Juri
quelle
118
Tatsächlich liegt der Fehler darin, dass es keine Möglichkeit gibt 0.1, eine endliche binäre Gleitkommazahl abzubilden .
Aaron Digulla
10
Die meisten Brüche können nicht mit exakter Genauigkeit in eine Dezimalzahl konvertiert werden. Eine gute Erklärung finden Sie hier: docs.python.org/release/2.5.1/tut/node16.html
Nate Zaugg
7
Mögliches Duplikat von Ist JavaScript's Math kaputt?
Epascarello
53
@SalmanA: Dass Ihre JavaScript-Laufzeit dieses Problem vor Ihnen verbirgt, bedeutet nicht, dass ich falsch liege.
Aaron Digulla
5
Nicht einverstanden mit Aaron, es gibt Möglichkeiten, 0.1 perfekt und vollständig binär zu codieren. IEEE 754 definiert dies jedoch nicht unbedingt. Stellen Sie sich eine Darstellung vor, in der Sie den ganzzahligen Teil einerseits binär, den dezimalen Teil andererseits bis zu n Dezimalstellen, auch binär, wie eine normale ganze Zahl> 0 und schließlich die Position des Dezimalpunkts codieren würden . Nun, Sie würden 0,1 perfekt und ohne Fehler darstellen. Übrigens, da JS intern eine endliche Anzahl von Dezimalstellen verwendet, könnten die Entwickler genauso gut die Eingeweide codieren, um diesen Fehler bei den letzten Dezimalstellen nicht zu machen.
Fabien Haddadi

Antworten:

469

Aus dem Gleitkomma-Leitfaden :

Was kann ich tun, um dieses Problem zu vermeiden?

Das hängt davon ab, welche Art von Berechnungen Sie durchführen.

  • Wenn Ihre Ergebnisse wirklich genau addiert werden müssen, insbesondere wenn Sie mit Geld arbeiten, verwenden Sie einen speziellen Dezimaldatentyp.
  • Wenn Sie nicht alle zusätzlichen Dezimalstellen sehen möchten, formatieren Sie Ihr Ergebnis bei der Anzeige einfach auf eine feste Anzahl von Dezimalstellen gerundet.
  • Wenn Sie keinen dezimalen Datentyp zur Verfügung haben, können Sie alternativ mit ganzen Zahlen arbeiten, z. B. Geldberechnungen vollständig in Cent durchführen. Dies ist jedoch mehr Arbeit und hat einige Nachteile.

Beachten Sie, dass der erste Punkt nur gilt, wenn Sie wirklich ein genaues Dezimalverhalten benötigen . Die meisten Leute brauchen das nicht, sie sind nur irritiert darüber, dass ihre Programme mit Zahlen wie 1/10 nicht richtig funktionieren, ohne zu bemerken, dass sie nicht einmal bei demselben Fehler blinken würden, wenn er mit 1/3 auftreten würde.

Wenn der erste Punkt wirklich auf Sie zutrifft, verwenden Sie BigDecimal für JavaScript , das überhaupt nicht elegant ist, aber das Problem tatsächlich löst, anstatt eine unvollständige Problemumgehung bereitzustellen.

Michael Borgwardt
quelle
11
Ich habe Ihren toten Link für BigDecimal bemerkt und auf der Suche nach einem Spiegel eine Alternative namens BigNumber gefunden: jsfromhell.com/classes/bignumber
Jacksonkr
4
@ bass-t: Ja, aber Floats können ganze Zahlen bis zur Länge des Signifikanten genau darstellen, und gemäß ECMA-Standard handelt es sich um einen 64-Bit-Float. Es kann also genau ganze Zahlen bis zu 2 ^ 52 darstellen
Michael Borgwardt
5
@Karl: Der Dezimalbruch 1/10 kann in Basis 2 nicht als endlicher binärer Bruch dargestellt werden, und das sind Javascript-Zahlen. Es ist also genau das gleiche Problem.
Michael Borgwardt
12
Ich habe heute gelernt, dass sogar ganze Zahlen Präzisionsprobleme in Javascript haben. Bedenken Sie, dass console.log(9332654729891549)tatsächlich gedruckt wird 9332654729891548(dh um eins!)
mlathe
12
@mlathe: Doh .. ;P... Zwischen 2⁵²= 4,503,599,627,370,496und 2⁵³= sind 9,007,199,254,740,992die darstellbaren Zahlen genau die ganzen Zahlen . Für den nächsten Bereich, von 2⁵³zu 2⁵⁴, alles wird multipliziert mit2 , so dass die darstellbaren Zahlen die sind auch solche , usw. Im Gegensatz zum vorherigen Bereich von 2⁵¹bis 2⁵²der Abstand ist 0.5, usw. Das ist aufgrund einfacher Erhöhung | die Basis abnimmt | radix 2 | binärer Exponent in / des 64-Bit-Float-Werts (was wiederum das selten dokumentierte 'unerwartete' Verhalten von toPrecision()für Werte zwischen 0und erklärt 1).
GitaarLAB
126

Ich mag die Lösung von Pedro Ladaria und verwende etwas Ähnliches.

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

Im Gegensatz zur Pedros-Lösung wird dies 0,999 aufrunden ... wiederholt und ist auf plus / minus eins auf der niedrigstwertigen Ziffer genau.

Hinweis: Wenn Sie mit 32- oder 64-Bit-Floats arbeiten, sollten Sie toPrecision (7) und toPrecision (15) verwenden, um die besten Ergebnisse zu erzielen. In dieser Frage finden Sie Informationen dazu, warum.

linux_mike
quelle
21
Gibt es einen Grund, warum Sie 12 ausgewählt haben?
QWertymk
18
toPrecisionGibt eine Zeichenfolge anstelle einer Zahl zurück. Dies ist möglicherweise nicht immer wünschenswert.
SStanley
7
parseFloat (1.005) .toPrecision (3) => 1.00
Peter
5
@ user2428118, ich weiß, ich wollte den Rundungsfehler zeigen, Das Ergebnis ist 1,00 statt 1,01
Peter
9
Was @ user2428118 sagte, ist möglicherweise nicht offensichtlich genug: (9.99*5).toPrecision(2)= 50 statt 49,95, da toPrecision die gesamte Zahl zählt, nicht nur Dezimalstellen. Sie können dann verwenden toPrecision(4), aber wenn Ihr Ergebnis> 100 ist, haben Sie wieder Pech, da die ersten drei Zahlen und eine Dezimalstelle auf diese Weise den Punkt verschieben und diesen mehr oder weniger unbrauchbar machen. Ich habe toFixed(2)stattdessen
aexl
79

Für die mathematisch geneigten: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

Der empfohlene Ansatz besteht darin, Korrekturfaktoren zu verwenden (multiplizieren Sie mit einer geeigneten Potenz von 10, damit die Arithmetik zwischen ganzen Zahlen erfolgt). Im Fall von 0.1 * 0.2ist beispielsweise der Korrekturfaktor 10und Sie führen die Berechnung durch:

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

Eine (sehr schnelle) Lösung sieht ungefähr so ​​aus:

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 diesem Fall:

> Math.m(0.1, 0.2)
0.02

Ich empfehle auf jeden Fall die Verwendung einer getesteten Bibliothek wie SinfulJS

SheetJS
quelle
1
Ich liebe diese elegante Problemumgehung, aber sie scheint nicht perfekt zu sein: jsfiddle.net/Dm6F5/1 Math.a (76,65, 38,45) gibt 115.10000000000002
nicolallias
3
Math.m (10,2332226616) gibt mir "-19627406800", was ein negativer Wert ist ... Ich hoffe, es muss eine Obergrenze geben - möglicherweise verursacht dies dieses Problem. Bitte vorschlagen
Shiva Komuravelly
1
Das alles sieht gut aus, scheint aber irgendwo einen oder zwei Fehler zu haben.
MrYellow
5
Sehr schnelle Lösung, sagte er ... kaputte Lösung, die niemand jemals gesagt hat.
Cozzbie
2
Verwenden Sie nicht den obigen Code. Es ist absolut keine "schnelle Lösung", wenn es nicht funktioniert. Dies ist eine mathematische Frage, daher ist Genauigkeit erforderlich.
Drenai
49

Führen Sie nur eine Multiplikation durch? Wenn ja, können Sie zu Ihrem Vorteil ein ordentliches Geheimnis der Dezimalarithmetik nutzen. Das heißt , dass NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals. Das heißt, wenn wir haben, 0.123 * 0.12dann wissen wir, dass es 5 Dezimalstellen geben wird, weil 0.123es 3 Dezimalstellen und 0.12zwei hat. Wenn uns JavaScript eine Zahl wie diese gibt 0.014760000002, können wir sicher auf die 5. Dezimalstelle runden, ohne befürchten zu müssen, dass die Genauigkeit verloren geht.

Nate Zaugg
quelle
6
... und wie man die genaue Anzahl von Dezimalstellen erhält .
Linie-o
7
0,5 · 0,2 = 0,10; Sie können immer noch mit 2 Dezimalstellen (oder weniger) abschneiden. Aber es wird niemals eine Zahl mit mathematischer Bedeutung geben, die über dieses Gesetz hinausgeht.
Nate Zaugg
3
Hast du ein Zitat dafür? Beachten Sie auch, dass dies nicht für die Teilung gilt.
Griffin
3
@NateZaugg Sie können die überlaufenden Dezimalstellen nicht abschneiden, Sie müssen den Betrag runden, da 2090.5 * 8.61 17999.205 ist, aber im Float ist es 17999.204999999998
Lostfields
3
@Lostfields - Du bist richtig! Ich habe meine Antwort aktualisiert.
Nate Zaugg
29

Sie suchen nach einer sprintfImplementierung für JavaScript, damit Sie Floats mit kleinen Fehlern (da sie im Binärformat gespeichert sind) in einem von Ihnen erwarteten Format schreiben können.

Versuchen Sie es mit Javascript-sprintf , Sie würden es so nennen:

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

um Ihre Zahl als Gleitkomma mit zwei Dezimalstellen auszudrucken.

Sie können Number.toFixed () auch zu Anzeigezwecken verwenden, wenn Sie nicht mehr Dateien nur zum Gleitkomma-Runden mit einer bestimmten Genauigkeit einschließen möchten .

Douglas
quelle
4
Ich denke, das ist die sauberste Lösung. Sofern das Ergebnis nicht wirklich 0,02 sein muss, ist der kleine Fehler vernachlässigbar. Es klingt so, als ob es wichtig ist, dass Ihre Nummer gut angezeigt wird und nicht, dass Sie eine willkürliche Genauigkeit haben.
Long Ouyang
2
Für die Anzeige ist dies in der Tat die beste Option. Für komplizierte Berechnungen überprüfen Sie die Antwort von Borgwardt.
Nicht verfügbar
4
Andererseits gibt dies genau die gleiche Zeichenfolge zurück wie yourNumber.toFixed (2).
Robert
27

Ich finde , dass BigNumber.js meinen Anforderungen entspricht.

Eine JavaScript-Bibliothek für Dezimal- und Nicht-Dezimal-Arithmetik mit beliebiger Genauigkeit.

Es hat eine gute Dokumentation und der Autor reagiert sehr fleißig auf Feedback.

Der gleiche Autor hat 2 ähnliche Bibliotheken:

Big.js

Eine kleine, schnelle JavaScript-Bibliothek für Dezimalarithmetik mit beliebiger Genauigkeit. Die kleine Schwester von bignumber.js.

und Decimal.js

Ein Dezimaltyp mit beliebiger Genauigkeit für JavaScript.

Hier ist ein Code mit 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>

Ronnie Overby
quelle
3
Die Verwendung einer Bibliothek ist meiner Meinung nach definitiv die beste Wahl.
Anthony
1
Von diesem Link github.com/MikeMcl/big.js/issues/45 bignumber.js -> finanzielle Dezimalzahl.js -> wissenschaftliche big.js -> ???
Vee
20
var times = function (a, b) {
    return Math.round((a * b) * 100)/100;
};

---oder---

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

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

---ebenfalls---

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

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

--- wie 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
shawndumas
quelle
4
Ich denke, das würde das gleiche Problem ergeben. Sie geben einen Gleitkomma zurück, sodass der Rückgabewert mit großer Wahrscheinlichkeit auch "falsch" ist.
Gertjan
1
Sehr klug und nützlich, +1.
Jonatas Walker
18

Diese Funktion ermittelt die erforderliche Genauigkeit aus der Multiplikation zweier Gleitkommazahlen und gibt ein Ergebnis mit der entsprechenden Genauigkeit zurück. Elegant ist es aber nicht.

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); 
}
Gabriel
quelle
Ew. Ja, konvertieren wir Zahlen in Zeichenfolgen für Gleitkomma-Mathematik und schlagen dies auch als Antwort vor.
Andrew
17

Überraschenderweise wurde diese Funktion noch nicht veröffentlicht, obwohl andere ähnliche Variationen davon haben. Es stammt aus den MDN-Webdokumenten für Math.round (). Es ist prägnant und ermöglicht unterschiedliche Präzision.

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

console.log (präziseRunde (1234.5678, 1)); // erwartete Ausgabe: 1234.6

console.log (präziseRunde (1234.5678, -1)); // erwartete Ausgabe: 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'>

UPDATE: 20.08.2019 Diesen Fehler gerade bemerkt. Ich glaube, es liegt an einem Gleitkomma-Genauigkeitsfehler mit Math.round ().

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

Diese Bedingungen funktionieren korrekt:

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;
}

Dies fügt nur eine Ziffer rechts hinzu, wenn Dezimalstellen gerundet werden. MDN hat die Math.round-Seite aktualisiert, sodass möglicherweise jemand eine bessere Lösung anbieten kann.

HelloWorldPeace
quelle
falsche Antwort. 10.2 gibt immer 10.19 zurück. jsbin.com/tozogiwide/edit?html,js,console,output
Žilvinas
@ Žilvinas Der von Ihnen gepostete JSBin-Link verwendet nicht die oben aufgeführte MDN-Funktion. Ich denke, Ihr Kommentar richtet sich an die falsche Person.
HelloWorldPeace
13

Sie müssen sich nur entscheiden, wie viele Dezimalstellen Sie tatsächlich möchten - Sie können den Kuchen nicht haben und ihn auch essen :-)

Bei jeder weiteren Operation häufen sich numerische Fehler, und wenn Sie sie nicht frühzeitig abschneiden, wird sie nur noch größer. Numerische Bibliotheken, die Ergebnisse präsentieren, die sauber aussehen, schneiden bei jedem Schritt einfach die letzten 2 Ziffern ab. Numerische Co-Prozessoren haben aus dem gleichen Grund auch eine "normale" und "volle" Länge. Manschetten sind für einen Prozessor billig, für Sie in einem Skript jedoch sehr teuer (Multiplizieren und Teilen und Verwenden von pov (...)). Eine gute Mathe-Bibliothek würde den Boden (x, n) bereitstellen, um den Cut-Off für Sie durchzuführen.

Zumindest sollten Sie also mit pov (10, n) eine globale Variable / Konstante erstellen - was bedeutet, dass Sie sich für die Genauigkeit entschieden haben, die Sie benötigen :-) Dann tun Sie:

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

Sie könnten auch weiter rechnen und erst am Ende abschneiden - vorausgesetzt, Sie zeigen nur an und machen keine if-s mit Ergebnissen. Wenn Sie dies tun können, ist .toFixed (...) möglicherweise effizienter.

Wenn Sie if-s / Vergleiche durchführen und nicht abschneiden möchten, benötigen Sie auch eine kleine Konstante, die normalerweise als eps bezeichnet wird und eine Dezimalstelle höher ist als der maximal erwartete Fehler. Angenommen, Ihr Cut-Off beträgt die letzten zwei Dezimalstellen - dann hat Ihr EPS 1 an dritter Stelle von der letzten (drittniedrigste Signifikanz) und Sie können damit vergleichen, ob das Ergebnis im erwarteten EPS-Bereich liegt (0,02 -eps <0,1) * 0,2 <0,02 + eps).

ZXX
quelle
Sie können auch 0,5 hinzufügen, um die Rundung eines armen Mannes durchzuführen: Math.floor (x * PREC_LIM + 0,5) / PREC_LIM
cmroanirgo
Beachten Sie aber, dass zB Math.floor(-2.1)ist -3. Also vielleicht zBMath[x<0?'ceil':'floor'](x*PREC_LIM)/PREC_LIM
MikeM
Warum floorstatt round?
Quinn Comendant
12

Sie können parseFloat()und toFixed()wenn Sie dieses Problem für eine kleine Operation umgehen möchten:

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;
Softwareddy
quelle
11

Die Funktion round () bei phpjs.org funktioniert gut: http://phpjs.org/functions/round

num = .01 + .06;  // yields 0.0699999999999
rnum = round(num,12); // yields 0.07
Tom
quelle
2
@jrg Konventionell werden Zahlen, die mit einer "5" enden, auf die nächste Gerade gerundet (da ein Auf- oder Abrunden immer zu einer Verzerrung Ihrer Ergebnisse führen würde). Daher sollte 4,725 auf zwei Dezimalstellen gerundet tatsächlich 4,72 betragen.
Mark A. Durham
9

0,6 * 3 es ist großartig!)) Für mich funktioniert das gut:

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

Sehr sehr einfach))

Олег Всильдеревьев
quelle
Würde das aber mit so etwas funktionieren 8.22e-8 * 1.3?
Paul Carlton
0,6 x 3 = 1,8, der Code, den Sie geben, ergibt 2 ... also nicht gut.
Zyo
@Zyo In diesem Fall wird 1,8 zurückgegeben. Wie hast du es gemacht?
Drenai
Interessant. Sie können die Multiplikations- und Divisionsoperatoren hier austauschen und es funktioniert auch.
Andrew
9

Beachten Sie, dass dieses Verhalten für den allgemeinen Gebrauch wahrscheinlich akzeptabel ist.
Das Problem tritt auf, wenn diese Gleitkommawerte verglichen werden, um eine geeignete Aktion zu bestimmen.
Mit dem Aufkommen von ES6 wird eine neue Konstante Number.EPSILONdefiniert, um die akzeptable Fehlergrenze zu bestimmen:
Anstatt den Vergleich wie folgt durchzuführen

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

Sie können eine benutzerdefinierte Vergleichsfunktion wie folgt definieren:

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

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

user10089632
quelle
In meinem Fall war Number.EPSILON zu klein, was zB0.9 !== 0.8999999761581421
Tom
8

Das Ergebnis ist korrekt und ziemlich konsistent über Gleitkommaimplementierungen in verschiedenen Sprachen, Prozessoren und Betriebssystemen hinweg. Das einzige, was sich ändert, ist der Grad der Ungenauigkeit, wenn der Gleitkomma tatsächlich doppelt (oder höher) ist.

0,1 in binären Gleitkommazahlen entspricht 1/3 in Dezimalzahl (dh 0,3333333333333 ... für immer), es gibt einfach keine genaue Möglichkeit, damit umzugehen.

Wenn Sie mit Floats arbeiten, erwarten Sie immer kleine Rundungsfehler, sodass Sie das angezeigte Ergebnis auch immer auf etwas Sinnvolles runden müssen. Im Gegenzug erhalten Sie eine sehr sehr schnelle und leistungsstarke Arithmetik, da alle Berechnungen in der nativen Binärdatei des Prozessors ausgeführt werden.

Meistens besteht die Lösung nicht darin, auf Festkomma-Arithmetik umzuschalten, hauptsächlich weil sie viel langsamer ist und Sie in 99% der Fälle einfach nicht die Genauigkeit benötigen. Wenn Sie mit Dingen zu tun haben, die diese Genauigkeit erfordern (z. B. Finanztransaktionen), ist Javascript wahrscheinlich sowieso nicht das beste Werkzeug (da Sie die Festkommatypen erzwingen möchten, ist eine statische Sprache wahrscheinlich besser ).

Sie suchen nach der eleganten Lösung, dann befürchte ich, dass dies der Fall ist: Floats sind schnell, haben aber kleine Rundungsfehler - immer rund auf etwas Sinnvolles, wenn Sie ihre Ergebnisse anzeigen.

Keith
quelle
8

Um dies zu vermeiden, sollten Sie mit ganzzahligen Werten anstelle von Gleitkommazahlen arbeiten. Wenn Sie also 2 Positionen präzise mit den Werten * 100 arbeiten möchten, verwenden Sie für 3 Positionen 1000. Bei der Anzeige verwenden Sie einen Formatierer, um das Trennzeichen einzufügen.

Viele Systeme lassen die Arbeit mit Dezimalstellen auf diese Weise aus. Dies ist der Grund, warum viele Systeme mit Cent (als Ganzzahl) anstelle von Dollar / Euro (als Gleitkomma) arbeiten.

Gertjan
quelle
7

Problem

Gleitkomma kann nicht alle Dezimalwerte exakt speichern. Bei Verwendung von Gleitkommaformaten treten also immer Rundungsfehler bei den Eingabewerten auf. Die Fehler an den Eingängen führen natürlich zu Fehlern an der Ausgabe. Im Fall einer diskreten Funktion oder eines diskreten Operators kann es große Unterschiede in der Ausgabe um den Punkt geben, an dem die Funktion oder der Operator diskret ist.

Eingabe und Ausgabe für Gleitkommawerte

Wenn Sie Gleitkommavariablen verwenden, sollten Sie sich dessen immer bewusst sein. Und jede Ausgabe, die Sie von einer Berechnung mit Gleitkommawerten wünschen, sollte immer formatiert / konditioniert werden, bevor sie in diesem Sinne angezeigt wird.
Wenn nur kontinuierliche Funktionen und Operatoren verwendet werden, reicht es häufig aus, auf die gewünschte Genauigkeit zu runden (nicht abschneiden). Standardformatierungsfunktionen zum Konvertieren von Floats in Zeichenfolgen erledigen dies normalerweise für Sie.
Da durch die Rundung ein Fehler hinzugefügt wird, der dazu führen kann, dass der Gesamtfehler mehr als die Hälfte der gewünschten Genauigkeit beträgt, sollte die Ausgabe basierend auf der erwarteten Genauigkeit der Eingaben und der gewünschten Genauigkeit der Ausgabe korrigiert werden. Du solltest

  • Runden Sie Eingaben auf die erwartete Genauigkeit oder stellen Sie sicher, dass keine Werte mit höherer Genauigkeit eingegeben werden können.
  • Fügen Sie den Ausgaben vor dem Runden / Formatieren einen kleinen Wert hinzu, der kleiner oder gleich 1/4 der gewünschten Genauigkeit und größer als der maximal erwartete Fehler ist, der durch Rundungsfehler bei der Eingabe und während der Berechnung verursacht wird. Ist dies nicht möglich, reicht die Kombination der Genauigkeit des verwendeten Datentyps nicht aus, um die gewünschte Ausgabegenauigkeit für Ihre Berechnung zu erzielen.

Diese beiden Dinge werden normalerweise nicht erledigt und in den meisten Fällen sind die Unterschiede, die dadurch verursacht werden, dass sie nicht ausgeführt werden, zu gering, um für die meisten Benutzer wichtig zu sein. Ich hatte jedoch bereits ein Projekt, bei dem die Ausgabe von den Benutzern ohne diese Korrekturen nicht akzeptiert wurde.

Diskrete Funktionen oder Operatoren (wie Modul)

Wenn diskrete Operatoren oder Funktionen beteiligt sind, sind möglicherweise zusätzliche Korrekturen erforderlich, um sicherzustellen, dass die Ausgabe wie erwartet ist. Das Runden und Hinzufügen kleiner Korrekturen vor dem Runden kann das Problem nicht lösen.
Möglicherweise ist eine spezielle Überprüfung / Korrektur der Zwischenberechnungsergebnisse unmittelbar nach Anwendung der diskreten Funktion oder des Operators erforderlich. Für einen bestimmten Fall (Moduloperator) siehe meine Antwort auf die Frage: Warum gibt der Moduloperator eine Bruchzahl in Javascript zurück?

Vermeiden Sie besser das Problem

Es ist oft effizienter, diese Probleme zu vermeiden, indem für solche Berechnungen Datentypen (Ganzzahl- oder Festkommaformate) verwendet werden, mit denen die erwartete Eingabe ohne Rundungsfehler gespeichert werden kann. Ein Beispiel dafür ist, dass Sie niemals Gleitkommawerte für Finanzberechnungen verwenden sollten.

Stefan Mondelaers
quelle
4

Schauen Sie sich die Festkomma-Arithmetik an . Es wird wahrscheinlich Ihr Problem lösen, wenn der Bereich der Zahlen, mit denen Sie arbeiten möchten, klein ist (z. B. Währung). Ich würde es auf ein paar Dezimalwerte abrunden, was die einfachste Lösung ist.

Marius
quelle
5
Das Problem ist nicht Gleitkomma gegen Festpunkt, das Problem ist binär gegen Dezimal.
Michael Borgwardt
4

Probieren Sie meine chiliadische Arithmetikbibliothek aus, die Sie hier sehen können . Wenn Sie eine spätere Version wünschen, kann ich Ihnen eine besorgen.

Robert L.
quelle
4

Sie können die meisten Dezimalbrüche mit binären Gleitkommatypen nicht genau darstellen (was ECMAScript zur Darstellung von Gleitkommawerten verwendet). Es gibt also keine elegante Lösung, es sei denn, Sie verwenden arithmetische Typen mit beliebiger Genauigkeit oder einen dezimalbasierten Gleitkommatyp. Beispielsweise verwendet die mit Windows gelieferte Calculator-App jetzt eine Arithmetik mit beliebiger Genauigkeit, um dieses Problem zu lösen .

MSN
quelle
4

Geben Sie hier die Bildbeschreibung ein

    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
Ashish Singhal
quelle
3

Sie haben Recht, der Grund dafür ist die begrenzte Genauigkeit von Gleitkommazahlen. Speichern Sie Ihre rationalen Zahlen als Division von zwei ganzzahligen Zahlen. In den meisten Situationen können Sie Zahlen ohne Genauigkeitsverlust speichern. Wenn Sie drucken möchten, möchten Sie das Ergebnis möglicherweise als Bruch anzeigen. Mit der von mir vorgeschlagenen Darstellung wird es trivial.

Natürlich hilft das bei irrationalen Zahlen nicht viel. Möglicherweise möchten Sie Ihre Berechnungen jedoch so optimieren, dass sie das geringste Problem verursachen (z sqrt(3)^2). B. das Erkennen von Situationen wie) .

Skalee
quelle
Sie haben Recht, der Grund dafür ist die begrenzte Genauigkeit von Gleitkommazahlen - <pedant>tatsächlich hat das OP sie auf ungenaue Gleitkommaoperationen zurückgesetzt, was falsch ist</pedant>
Uhr
3

Ich hatte ein böses Rundungsfehlerproblem mit Mod 3. Manchmal, wenn ich 0 bekommen sollte, bekam ich .000 ... 01. Das ist einfach zu handhaben, testen Sie einfach auf <= .01. Aber manchmal bekam ich 2.99999999999998. AUTSCH!

BigNumbers löste das Problem, führte jedoch ein anderes, etwas ironisches Problem ein. Beim Versuch, 8.5 in BigNumbers zu laden, wurde ich informiert, dass es wirklich 8.4999 war… und mehr als 15 signifikante Ziffern hatte. Dies bedeutete, dass BigNumbers es nicht akzeptieren konnte (ich glaube, ich erwähnte, dass dieses Problem etwas ironisch war).

Einfache Lösung für das ironische Problem:

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);
mcgeo52
quelle
2

Use Number (1.234443) .toFixed (2); es wird 1.23 gedruckt

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

decimal.js , big.js oder bignumber.js können verwendet werden, um Gleitkomma-Manipulationsprobleme in Javascript zu vermeiden:

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: minimalistisch; Einfach zu verwenden; Genauigkeit in Dezimalstellen; Präzision nur für die Teilung.

bignumber.js: Basen 2-64; Einstellmöglichkeiten; NaN; Unendlichkeit; Genauigkeit in Dezimalstellen; Präzision nur für die Teilung; Basispräfixe.

decimal.js: Basen 2-64; Einstellmöglichkeiten; NaN; Unendlichkeit; nicht ganzzahlige Potenzen, exp, ln, log; Genauigkeit in signifikanten Ziffern angegeben; Präzision immer angewendet; zufällige Zahlen.

Link zu detaillierten Vergleichen

Erikas Pliauksta
quelle
2

Elegant, vorhersehbar und wiederverwendbar

Lassen Sie uns das Problem auf elegante und wiederverwendbare Weise behandeln. Mit den folgenden sieben Zeilen können Sie auf die gewünschte Gleitkommapräzision für eine beliebige Zahl zugreifen, indem Sie einfach .decimalan das Ende der Zahl, Formel oder integrierten MathFunktion anhängen .

// 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

Prost!

Bernesto
quelle
2
Wenn Sie sich für eine Ablehnung entscheiden, geben Sie zumindest einen Grund an.
Bernesto
1

Verwenden

var x = 0.1*0.2;
 x =Math.round(x*Math.pow(10,2))/Math.pow(10,2);
Himadri
quelle
4
Hmm ... aber beachten Sie, dass dies immer auf 2 Dezimalstellen gerundet wird. Das wäre natürlich eine Option, aber was ist mit der Berechnung 0,55 * 0,55 (da ich die genauen Zahlen nicht im Voraus kenne. Das würde 0,3 statt 0,3025 ergeben. Natürlich könnte ich dann verwenden Math.round(x*Math.pow(10,4))/Math.pow(10,4);. Rundung ist immer eine Option, aber ich wollte nur wissen, ob es eine bessere Lösung gibt
Juri
10.2 gibt immer 10.19 zurück jsbin.com/tozogiwide/edit?html,js,console,output
Žilvinas
1

nicht elegant, erledigt aber den Job (entfernt nachgestellte Nullen)

var num = 0.1*0.2;
alert(parseFloat(num.toFixed(10))); // shows 0.02
Peter
quelle
6
toFixed funktioniert nicht immer: stackoverflow.com/questions/661562/…
Sam Hasler
1

Das funktioniert bei mir:

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
Antonio Max
quelle
1
leider ist round_up (4.15,2) => 4.16.
jrg
1

Ausgabe mit folgender Funktion:

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();

Achten Sie auf die Ausgabe toFixedCurrency(x).

Júlio Paulillo
quelle