832 Stimmen

Wie geht man mit der Genauigkeit von Fließkommazahlen in JavaScript um?

Ich habe das folgende Dummy-Testskript:

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

So wird das Ergebnis gedruckt 0.020000000000000004 während es einfach drucken sollte 0.02 (wenn Sie Ihren Taschenrechner benutzen). Soweit ich verstanden habe, ist dies auf Fehler in der Genauigkeit der Fließkommamultiplikation zurückzuführen.

Hat jemand eine gute Lösung, damit ich in einem solchen Fall das richtige Ergebnis erhalte? 0.02 ? Ich weiß, es gibt Funktionen wie toFixed oder Rundung wäre eine weitere Möglichkeit, aber ich möchte wirklich die ganze Zahl ohne Kürzung und Rundung gedruckt haben. Ich wollte nur wissen, ob jemand von Ihnen eine schöne, elegante Lösung hat.

Natürlich werde ich sonst auf 10 Stellen oder so aufrunden.

157 Stimmen

Eigentlich liegt der Fehler daran, dass es keine Möglichkeit gibt, die 0.1 in eine endliche binäre Gleitkommazahl.

19 Stimmen

Die meisten Brüche lassen sich nicht mit exakter Genauigkeit in eine Dezimalzahl umwandeln. Eine gute Erklärung finden Sie hier: docs.python.org/release/2.5.1/tut/node16.html

8 Stimmen

8voto

Peter Punkte 5010

Nicht elegant, aber zweckmäßig (entfernt die Nullen am Ende)

var num = 0.1*0.2;
alert(parseFloat(num.toFixed(10))); // shows 0.02

8voto

Simon Punkte 2276

Ich habe das Problem gelöst, indem ich zunächst beide Zahlen in ganze Zahlen umgewandelt habe, den Ausdruck ausgeführt habe und anschließend das Ergebnis geteilt habe, um die Dezimalstellen zurückzubekommen:

function evalMathematicalExpression(a, b, op) {
    const smallest = String(a < b ? a : b);
    const factor = smallest.length - smallest.indexOf('.');

    for (let i = 0; i < factor; i++) {
        b *= 10;
        a *= 10;
    }

    a = Math.round(a);
    b = Math.round(b);
    const m = 10 ** factor;
    switch (op) {
        case '+':
            return (a + b) / m;
        case '-':
            return (a - b) / m;
        case '*':
            return (a * b) / (m ** 2);
        case '/':
            return a / b;
    }

    throw `Unknown operator ${op}`;
}

Ergebnisse für verschiedene Operationen (die ausgeschlossenen Zahlen sind Ergebnisse aus eval ):

0.1 + 0.002   = 0.102 (0.10200000000000001)
53 + 1000     = 1053 (1053)
0.1 - 0.3     = -0.2 (-0.19999999999999998)
53 - -1000    = 1053 (1053)
0.3 * 0.0003  = 0.00009 (0.00008999999999999999)
100 * 25      = 2500 (2500)
0.9 / 0.03    = 30 (30.000000000000004)
100 / 50      = 2 (2)

8voto

Gertjan Punkte 852

Um dies zu vermeiden, sollten Sie mit ganzzahligen Werten anstelle von Fließkommazahlen arbeiten. Wenn Sie also eine Genauigkeit von 2 Positionen wünschen, arbeiten Sie mit den Werten * 100, für 3 Positionen verwenden Sie 1000. Bei der Anzeige verwenden Sie einen Formatierer, um das Trennzeichen einzufügen.

Viele Systeme verzichten auf diese Weise auf die Arbeit mit Dezimalzahlen. Aus diesem Grund arbeiten viele Systeme mit Cents (als Ganzzahl) statt mit Dollar/Euros (als Fließkommazahl).

7voto

Stefan Mondelaers Punkte 701

Problem

Fließkommazahlen können nicht alle Dezimalwerte exakt speichern. Bei der Verwendung von Fließkommaformaten kommt es daher immer zu Rundungsfehlern bei den Eingabewerten. Die Fehler bei den Eingaben führen natürlich auch zu Fehlern bei der Ausgabe. Im Falle einer diskreten Funktion oder eines diskreten Operators kann es bei der Ausgabe große Unterschiede um den Punkt herum geben, an dem die Funktion oder der Operator diskret ist.

Eingabe und Ausgabe von Fließkommazahlen

Bei der Verwendung von Fließkomma-Variablen sollten Sie sich dessen also immer bewusst sein. Und was auch immer Sie aus einer Berechnung mit Fließkommazahlen ausgeben möchten, sollte vor der Anzeige immer entsprechend formatiert/konditioniert werden.
Wenn nur kontinuierliche Funktionen und Operatoren verwendet werden, reicht es oft aus, auf die gewünschte Genauigkeit zu runden (nicht abzuschneiden). Die Standardformatierungsfunktionen für die Konvertierung von Fließkommazahlen in Zeichenketten erledigen dies normalerweise für Sie.
Da die Rundung einen Fehler hinzufügt, der dazu führen kann, dass der Gesamtfehler mehr als die Hälfte der gewünschten Genauigkeit beträgt, sollte die Ausgabe auf der Grundlage der erwarteten Genauigkeit der Eingaben und der gewünschten Genauigkeit der Ausgabe korrigiert werden. Sie sollten

  • Runden Sie die 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 zu erwartende Fehler durch Rundungsfehler bei der Eingabe und während der Berechnung ist. Wenn dies nicht möglich ist, reicht die Kombination der Genauigkeit des verwendeten Datentyps nicht aus, um die gewünschte Ausgabegenauigkeit für Ihre Berechnung zu erreichen.

Diese beiden Dinge werden in der Regel nicht gemacht, und in den meisten Fällen sind die Unterschiede, die dadurch entstehen, zu gering, um für die meisten Benutzer von Bedeutung zu sein, aber ich hatte bereits ein Projekt, bei dem die Ausgabe ohne diese Korrekturen von den Benutzern nicht akzeptiert wurde.

Diskrete Funktionen oder Operatoren (wie Modula)

Wenn es sich um diskrete Operatoren oder Funktionen handelt, sind möglicherweise zusätzliche Korrekturen erforderlich, um sicherzustellen, dass die Ausgabe den Erwartungen entspricht. Das Runden und Hinzufügen kleiner Korrekturen vor dem Runden kann das Problem nicht lösen.
Eine besondere Prüfung/Korrektur von Berechnungszwischenergebnissen unmittelbar nach Anwendung der diskreten Funktion oder des Operators kann erforderlich sein. Für einen speziellen Fall (Modula-Operator), siehe meine Antwort auf die Frage: Warum gibt der Modulus-Operator eine Bruchzahl in Javascript zurück?

Besser das Problem vermeiden

Es ist oft effizienter, diese Probleme zu vermeiden, indem man für solche Berechnungen Datentypen (Ganzzahl- oder Festkommaformate) verwendet, die die erwartete Eingabe ohne Rundungsfehler speichern können. Ein Beispiel dafür ist, dass Sie niemals Fließkommazahlen für Finanzberechnungen verwenden sollten.

5voto

Bernesto Punkte 1095

Elegant, vorhersehbar und wiederverwendbar

Lassen Sie uns das Problem auf eine elegante und wiederverwendbare Weise lösen. Mit den folgenden sieben Zeilen können Sie die gewünschte Fließkommagenauigkeit für jede beliebige Zahl erreichen, indem Sie einfach Folgendes anhängen .decimal an das Ende der Zahl, der Formel oder des eingebauten Math Funktion.

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

Zum Wohl!

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X