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

605voto

Michael Borgwardt Punkte 334642

Von der Fließkomma-Leitfaden :

Was kann ich tun, um dieses Problem zu vermeiden?

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

  • Wenn Sie Ihre Ergebnisse wirklich genau addieren müssen, insbesondere wenn Sie mit Geld arbeiten: verwenden Sie ein spezielles Dezimalsystem Datentyp.
  • Wenn Sie all diese zusätzlichen Dezimalstellen nicht sehen wollen, formatieren Sie einfach Ihr Ergebnis gerundet mit einer festen Anzahl von Dezimalstellen, wenn anzeigen.
  • Wenn Sie keinen dezimalen Datentyp zur Verfügung haben, können Sie alternativ mit mit ganzen Zahlen zu arbeiten, z. B. für Geld Berechnungen ausschließlich in Cents durchzuführen. Aber das ist mehr Arbeit und hat einige Nachteile.

Beachten Sie, dass der erste Punkt nur dann zutrifft, wenn Sie wirklich spezifische, präzise dezimal Verhalten. Die meisten Leute brauchen das nicht, sie ärgern sich nur darüber, dass ihre Programme mit Zahlen wie 1/10 nicht richtig funktionieren, ohne sich darüber im Klaren zu sein, dass sie nicht einmal mit der Wimper zucken würden, wenn der gleiche Fehler bei 1/3 auftreten würde.

Wenn der erste Punkt wirklich auf Sie zutrifft, verwenden Sie BigDecimal für JavaScript o DecimalJS , die das Problem tatsächlich löst und nicht nur eine unvollkommene Abhilfe schafft.

195voto

linux_mike Punkte 2323

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

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

Im Gegensatz zu Pedros Lösung rundet diese auf 0,999... auf und ist auf plus/minus eins der niederwertigsten Stelle genau.

Hinweis: Wenn Sie mit 32- oder 64-Bit-Fließkommazahlen arbeiten, sollten Sie toPrecision(7) und toPrecision(15) verwenden, um optimale Ergebnisse zu erzielen. Siehe diese Frage für Informationen darüber, warum.

95voto

SheetJS Punkte 21311

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

Es wird empfohlen, Korrekturfaktoren zu verwenden (Multiplikation mit einer geeigneten 10er-Potenz, damit die Arithmetik zwischen ganzen Zahlen erfolgt). Zum Beispiel, im Fall von 0.1 * 0.2 beträgt der Korrekturfaktor 10 und 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 etwa 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

57voto

Nate Zaugg Punkte 4132

Führen Sie nur die Multiplikation durch? Wenn ja, dann können Sie ein nettes Geheimnis der Dezimalarithmetik zu Ihrem Vorteil nutzen. Nämlich, dass NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals . Das bedeutet, dass wir, wenn wir 0.123 * 0.12 dann wissen wir, dass es 5 Dezimalstellen geben wird, weil 0.123 hat 3 Dezimalstellen und 0.12 hat zwei. Wenn JavaScript uns also eine Zahl gibt wie 0.014760000002 können wir sicher auf die 5. Dezimalstelle runden, ohne einen Präzisionsverlust befürchten zu müssen.

56voto

HelloWorldPeace Punkte 1157

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

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

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

console.log(precisionRound(1234.5678, -1));
// expected output: 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

Ich habe gerade diesen Fehler bemerkt. Ich glaube, es liegt an einem Fließkomma-Präzisionsfehler bei 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

Reparieren:

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

Damit wird beim Runden von Dezimalzahlen einfach eine Ziffer nach rechts hinzugefügt. MDN hat die Math.round() Seite, vielleicht kann jemand eine bessere Lösung anbieten.

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