847 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.

158 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

9voto

0,6 * 3 es ist fantastisch!)) Bei mir funktioniert das gut:

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

Sehr, sehr einfach))

8voto

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.

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

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

5voto

Franck Freiburger Punkte 23512

Meiner Meinung nach geht es hier darum, die fp-Zahl zu runden, um eine schöne/kurze Standard-String-Darstellung zu erhalten.

Die 53-Bit-Signifikantengenauigkeit ergibt eine Genauigkeit von 15 bis 17 signifikanten Dezimalstellen (253 1,11 × 1016). Wird eine dezimale Zeichenkette mit höchstens 15 signifikanten Ziffern in die IEEE-754-Doppelpräzisionsdarstellung umgewandelt, konvertiert und anschließend wieder in eine Dezimalzeichenfolge mit der gleichen Anzahl von Stellen umgewandelt, sollte das Endergebnis mit der ursprünglichen Zeichenfolge übereinstimmen. Wenn eine IEEE 754-Zahl mit doppelter Genauigkeit in eine Dezimalzeichenfolge mit mindestens 17 signifikanten Stellen umgewandelt wird, konvertiert und dann wieder in die double-precision-Darstellung umgewandelt, muss das Endergebnis mit der ursprünglichen Zahl übereinstimmen.
...
Mit den 52 Bits des Signifikanten des Bruchs (F), die im Speicherformat erscheinen, beträgt die Gesamtgenauigkeit also 53 Bits (etwa 16 Dezimalstellen, 53 log10(2) 15,955). Die Bits sind wie folgt angeordnet ... wikipedia

(0.1).toPrecision(100) ->
0.1000000000000000055511151231257827021181583404541015625000000000000000000000000000000000000000000000

(0.1+0.2).toPrecision(100) ->
0.3000000000000000444089209850062616169452667236328125000000000000000000000000000000000000000000000000

Soweit ich weiß, können wir dann den Wert auf 15 Stellen aufrunden, um eine schöne Zeichenkettendarstellung zu erhalten.

10**Math.floor(53 * Math.log10(2)) // 1e15

z. B.

Math.round((0.2+0.1) * 1e15 ) / 1e15
0.3

(Math.round((0.2+0.1) * 1e15 ) / 1e15).toPrecision(100)
0.2999999999999999888977697537484345957636833190917968750000000000000000000000000000000000000000000000

Das wäre die Funktion:

function roundNumberToHaveANiceDefaultStringRepresentation(num) {

    const integerDigits = Math.floor(Math.log10(Math.abs(num))+1);
    const mult = 10**(15-integerDigits); // also consider integer digits
    return Math.round(num * mult) / mult;
}

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