Ich habe das folgende Dummy-Testskript:
function test() {
var x = 0.1 * 0.2;
document.write(x);
}
test();
Dadurch wird das Ergebnis gedruckt, 0.020000000000000004
wä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 toFixed
oder 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.
0.1
, eine endliche binäre Gleitkommazahl abzubilden .Antworten:
Aus dem Gleitkomma-Leitfaden :
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.
quelle
console.log(9332654729891549)
tatsächlich gedruckt wird9332654729891548
(dh um eins!);P
... Zwischen2⁵²
=4,503,599,627,370,496
und2⁵³
= sind9,007,199,254,740,992
die darstellbaren Zahlen genau die ganzen Zahlen . Für den nächsten Bereich, von2⁵³
zu2⁵⁴
, alles wird multipliziert mit2
, so dass die darstellbaren Zahlen die sind auch solche , usw. Im Gegensatz zum vorherigen Bereich von2⁵¹
bis2⁵²
der Abstand ist0.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 vontoPrecision()
für Werte zwischen0
und erklärt1
).Ich mag die Lösung von Pedro Ladaria und verwende etwas Ähnliches.
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.
quelle
toPrecision
Gibt eine Zeichenfolge anstelle einer Zahl zurück. Dies ist möglicherweise nicht immer wünschenswert.(9.99*5).toPrecision(2)
= 50 statt 49,95, da toPrecision die gesamte Zahl zählt, nicht nur Dezimalstellen. Sie können dann verwendentoPrecision(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 habetoFixed(2)
stattdessenFü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.2
ist beispielsweise der Korrekturfaktor10
und Sie führen die Berechnung durch:Eine (sehr schnelle) Lösung sieht ungefähr so aus:
In diesem Fall:
Ich empfehle auf jeden Fall die Verwendung einer getesteten Bibliothek wie SinfulJS
quelle
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.12
dann wissen wir, dass es 5 Dezimalstellen geben wird, weil0.123
es 3 Dezimalstellen und0.12
zwei hat. Wenn uns JavaScript eine Zahl wie diese gibt0.014760000002
, können wir sicher auf die 5. Dezimalstelle runden, ohne befürchten zu müssen, dass die Genauigkeit verloren geht.quelle
Sie suchen nach einer
sprintf
Implementierung 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:
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 .
quelle
Ich finde , dass BigNumber.js meinen Anforderungen entspricht.
Es hat eine gute Dokumentation und der Autor reagiert sehr fleißig auf Feedback.
Der gleiche Autor hat 2 ähnliche Bibliotheken:
Big.js
und Decimal.js
Hier ist ein Code mit BigNumber:
quelle
---oder---
---ebenfalls---
--- wie in ---
quelle
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.
quelle
Ü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.
console.log (präziseRunde (1234.5678, 1)); // erwartete Ausgabe: 1234.6
console.log (präziseRunde (1234.5678, -1)); // erwartete Ausgabe: 1230
UPDATE: 20.08.2019 Diesen Fehler gerade bemerkt. Ich glaube, es liegt an einem Gleitkomma-Genauigkeitsfehler mit Math.round ().
Diese Bedingungen funktionieren korrekt:
Fix:
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.
quelle
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:
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).
quelle
Math.floor(-2.1)
ist-3
. Also vielleicht zBMath[x<0?'ceil':'floor'](x*PREC_LIM)/PREC_LIM
floor
stattround
?Sie können
parseFloat()
undtoFixed()
wenn Sie dieses Problem für eine kleine Operation umgehen möchten:quelle
Die Funktion round () bei phpjs.org funktioniert gut: http://phpjs.org/functions/round
quelle
0,6 * 3 es ist großartig!)) Für mich funktioniert das gut:
Sehr sehr einfach))
quelle
8.22e-8 * 1.3
?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.EPSILON
definiert, um die akzeptable Fehlergrenze zu bestimmen:Anstatt den Vergleich wie folgt durchzuführen
Sie können eine benutzerdefinierte Vergleichsfunktion wie folgt definieren:
Quelle: http://2ality.com/2015/04/numbers-math-es6.html#numberepsilon
quelle
0.9 !== 0.8999999761581421
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.
quelle
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.
quelle
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
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.
quelle
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.
quelle
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.
quelle
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 .
quelle
quelle
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) .quelle
<pedant>
tatsächlich hat das OP sie auf ungenaue Gleitkommaoperationen zurückgesetzt, was falsch ist</pedant>
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:
quelle
Use Number (1.234443) .toFixed (2); es wird 1.23 gedruckt
quelle
decimal.js , big.js oder bignumber.js können verwendet werden, um Gleitkomma-Manipulationsprobleme in Javascript zu vermeiden:
Link zu detaillierten Vergleichen
quelle
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
.decimal
an das Ende der Zahl, Formel oder integriertenMath
Funktion anhängen .Prost!
quelle
Verwenden
quelle
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 gibtnicht elegant, erledigt aber den Job (entfernt nachgestellte Nullen)
quelle
Das funktioniert bei mir:
quelle
Ausgabe mit folgender Funktion:
Achten Sie auf die Ausgabe
toFixedCurrency(x)
.quelle