Präzise finanzielle Berechnung in JavaScript. Was sind die Gotchas?

Im Interesse plattformübergreifenden Codes möchte ich eine einfache Finanzanwendung in JavaScript entwickeln. Die erforderlichen Berechnungen beinhalten Zinseszins und relativ lange Dezimalzahlen. Ich würde gerne wissen, welche Fehler zu vermeiden sind, wenn man JavaScript benutzt, um diese Art von Mathe zu machen – wenn es überhaupt möglich ist!

Sie sollten Ihre Dezimalwerte wahrscheinlich um 100 skalieren und alle Geldwerte in ganzen Cent darstellen. Dies dient dazu, Probleme mit Gleitkommalogik und Arithmetik zu vermeiden. In JavaScript gibt es keinen dezimalen Datentyp – der einzige numerische Datentyp ist Gleitkomma. Daher wird generell empfohlen, Geld mit 2550 Cent anstatt mit 25.50 Dollar zu handeln.

Bedenken Sie das in JavaScript:

 var result = 1.0 + 2.0; // (result === 3.0) returns true 

Aber:

 var result = 0.1 + 0.2; // (result === 0.3) returns false 

Der Ausdruck 0.1 + 0.2 === 0.3 gibt false , aber glücklicherweise ist die Ganzzahlarithmetik im Fließkomma genau, so dass Dezimaldarstellungserrors durch Skalieren von 1 vermieden werden können.

Beachten Sie, dass, während die Menge der reellen Zahlen unendlich ist, nur eine endliche Anzahl von ihnen (18.437.736.874.454.810.627 um genau zu sein) genau durch das Fließkomma-Format von JavaScript dargestellt werden kann. Daher ist die Darstellung der anderen Zahlen eine Annäherung an die tatsächliche Zahl 2 .


1 Douglas Crockford: JavaScript: Die guten Teile : Anhang A – Schreckliche Teile (Seite 105) .
2 David Flanagan: JavaScript: Der definitive Leitfaden, vierte Ausgabe : 3.1.3 Fließkomma-Literale (Seite 31) .

Die Skalierung jedes Werts um 100 ist die Lösung. Von Hand zu tun ist wahrscheinlich nutzlos, da Sie Bibliotheken finden, die das für Sie tun. Ich empfehle moneysafe, das eine funktionale API bietet, die sich gut für ES6-Anwendungen eignet:

 const { in$, $ } = require('moneysafe'); console.log(in$($(10.5) + $(.3)); // 10.8 

https://github.com/ericelliott/moneysafe

functioniert sowohl in Node.js als auch im Browser.

Es gibt keine “genaue” finanzielle Berechnung wegen nur zwei Dezimalbruchstellen, aber das ist ein allgemeineres Problem.

In JavaScript können Sie jeden Wert um 100 Math.round() und Math.round() wenn ein Bruch auftreten kann.

Sie können ein Objekt verwenden, um die Zahlen zu speichern und die Rundung in die Prototyp-Methode ” valueOf() . So was:

 sys = require('sys'); var Money = function(amount) { this.amount = amount; } Money.prototype.valueOf = function() { return Math.round(this.amount*100)/100; } var m = new Money(50.42355446); var n = new Money(30.342141); sys.puts(m.amount + n.amount); //80.76569546 sys.puts(m+n); //80.76 

Auf diese Weise wird jedes Mal, wenn Sie ein Money-Objekt verwenden, es auf zwei Dezimalstellen gerundet dargestellt. Der ungerundete Wert ist weiterhin über m.amount .

Sie können Ihren eigenen Rundungsalgorithmus in Money.prototype.valueOf() , wenn Sie Money.prototype.valueOf() .

Ihr Problem rührt von Ungenauigkeiten bei Gleitkommaberechnungen her. Wenn Sie nur eine Rundung verwenden, um dies zu lösen, haben Sie größere Fehler beim Multiplizieren und Dividieren.

Die Lösung ist unten, eine Erklärung folgt:

Du musst über die Mathematik dahinter nachdenken, um es zu verstehen. Reelle Zahlen wie 1/3 können nicht in Mathe mit Dezimalwerten dargestellt werden, da sie endlos sind (zB – .333333333333333 …). Einige Dezimalzahlen können nicht korrekt in Binärzahlen dargestellt werden. Zum Beispiel kann 0.1 mit einer begrenzten Anzahl von Ziffern nicht korrekt in Binärdarstellung dargestellt werden.

Eine detailliertere Beschreibung finden Sie hier: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

Sehen Sie sich die Lösungsimplementierung an: http://floating-point-gui.de/languages/javascript/