Beim Erstellen einer Anwendung, die sich mit vielen mathematischen Berechnungen befasst, bin ich auf das Problem gestoßen, dass bestimmte Zahlen Rundungsfehler verursachen.
Ich verstehe zwar, dass Fließkommazahlen nicht exakt sind , aber das Problem besteht darin, wie ich mit exakten Zahlen umgehe, um sicherzustellen, dass beim Ausführen von Berechnungen mit Fließkomma-Rundungen keine Probleme auftreten.
distanceTraveled(startVel, duration, acceleration)
würde getestet.Antworten:
Es gibt drei grundlegende Ansätze zum Erstellen alternativer numerischer Typen, die frei von Gleitkommarundungen sind. Das gemeinsame Thema bei diesen ist, dass sie stattdessen auf verschiedene Arten Ganzzahl-Mathematik verwenden.
Rationals
Stellen Sie die Zahl als Ganzes und als rationale Zahl mit einem Zähler und einem Nenner dar. Die Nummer
15.589
würde dargestellt alsw: 15; n: 589; d:1000
.Bei einer
w: 0; n: 1; d: 4
Addition auf 0,25 (das heißt ) wird der LCM berechnet und anschließend die beiden Zahlen addiert. Dies funktioniert in vielen Situationen gut, kann jedoch zu sehr großen Zahlen führen, wenn Sie mit vielen rationalen Zahlen arbeiten, die relativ zueinander primieren.Fixpunkt
Sie haben den ganzen Teil und den Dezimalteil. Alle Zahlen sind auf diese Genauigkeit gerundet (es gibt das Wort - aber Sie wissen, wo es ist). Sie könnten zum Beispiel einen festen Punkt mit 3 Dezimalstellen haben.
15.589
+0.250
wird addiert589 + 250 % 1000
für den Dezimalteil (und dann ein beliebiger Übertrag für den gesamten Teil). Dies funktioniert sehr gut mit vorhandenen Datenbanken. Wie bereits erwähnt, gibt es eine Rundung, aber Sie wissen, wo sie sich befindet, und können sie präziser als erforderlich angeben (Sie messen nur mit 3 Dezimalstellen, also korrigieren Sie sie mit 4).Floating Fixpunkt
Speichern Sie einen Wert und die Genauigkeit.
15.589
wird wie15589
für den Wert und3
für die Genauigkeit0.25
gespeichert , während als25
und gespeichert wird2
. Dies kann mit beliebiger Präzision umgehen. Ich glaube, das ist es, was die Interna von Javas BigDecimal verwenden (haben es in letzter Zeit nicht angeschaut). Irgendwann werden Sie es wieder aus diesem Format entfernen und anzeigen wollen - und das kann mit einer Rundung einhergehen (wiederum steuern Sie, wo es sich befindet).Sobald Sie die Auswahl für die Darstellung festgelegt haben, können Sie entweder vorhandene Bibliotheken von Drittanbietern finden, die diese verwenden, oder Ihre eigenen erstellen. Wenn Sie Ihre eigenen schreiben, stellen Sie sicher, dass Sie die Einheit testen und sicherstellen, dass Sie die Mathematik korrekt ausführen.
quelle
Wenn Gleitkommawerte Rundungsprobleme haben und Sie nicht auf Rundungsprobleme stoßen möchten, folgt logischerweise, dass die einzige Vorgehensweise darin besteht, keine Gleitkommawerte zu verwenden.
Nun stellt sich die Frage: "Wie kann ich mit nicht ganzzahligen Werten ohne Gleitkommavariablen rechnen?" Die Antwort liegt bei Datentypen mit willkürlicher Genauigkeit . Berechnungen sind langsamer, weil sie in Software anstatt in Hardware implementiert werden müssen, aber sie sind genau. Sie haben nicht angegeben, welche Sprache Sie verwenden, daher kann ich kein Paket empfehlen, aber für die meisten gängigen Programmiersprachen sind beliebige Präzisionsbibliotheken verfügbar.
quelle
lot of mathematical calculations
ist weder hilfreich noch die gegebenen Antworten. In den allermeisten Fällen (wenn Sie nicht mit Währungen zu tun haben) sollte Float wirklich ausreichen.Fließkomma-Arithmetik ist normalerweise ziemlich genau (15 Dezimalstellen für a
double
) und ziemlich flexibel. Die Probleme treten auf, wenn Sie mathematische Aufgaben ausführen, bei denen die Anzahl der Stellen für die Genauigkeit erheblich verringert wird. Hier sind einige Beispiele:Abbruch bei Subtraktion:
1234567890.12345 - 1234567890.12300
Das Ergebnis0.0045
hat nur zwei Dezimalstellen Genauigkeit. Dies trifft immer dann zu, wenn Sie zwei Zahlen gleicher Größe abziehen.Verschlucken von Präzision:
1234567890.12345 + 0.123456789012345
Wertet bis aus1234567890.24691
, die letzten zehn Stellen des zweiten Operanden gehen verloren.Multiplikationen: Wenn Sie zwei 15-stellige Zahlen multiplizieren, müssen 30 Stellen gespeichert werden. Sie können sie jedoch nicht speichern, sodass die letzten 15 Bits verloren gehen. Dies ist besonders lästig in Kombination mit
sqrt()
(wie insqrt(x*x + y*y)
: Das Ergebnis hat nur eine Genauigkeit von 7,5 Stellen.Dies sind die wichtigsten Fallstricke, die Sie beachten müssen. Und sobald Sie sich ihrer bewusst sind, können Sie versuchen, Ihre Mathematik so zu formulieren, dass sie sie vermeidet. Wenn Sie beispielsweise einen Wert in einer Schleife immer wieder inkrementieren müssen, vermeiden Sie Folgendes:
Nach ein paar Iterationen wird der größere
f
Teil der Präzision von verschlucktdf
. Schlimmer noch, die Fehler summieren sich und führen zu der kontraintuitiven Situation, dass ein kleinerer Fehlerdf
zu schlechteren Gesamtergebnissen führen kann. Schreiben Sie besser folgendes:Da Sie die Inkremente in einer einzigen Multiplikation kombinieren, ist das Ergebnis
f
auf 15 Dezimalstellen genau.Dies ist nur ein Beispiel. Es gibt andere Möglichkeiten, um Genauigkeitsverluste aus anderen Gründen zu vermeiden. Aber es hilft schon viel, über die Größe der beteiligten Werte nachzudenken und sich vorzustellen, was passieren würde, wenn Sie mit Stift und Papier rechnen und nach jedem Schritt auf eine feste Anzahl von Ziffern runden würden.
quelle
So stellen Sie sicher, dass Sie keine Probleme haben: Informieren Sie sich über Gleitkomma-Rechenprobleme, stellen Sie jemanden ein, der dies tut, oder verwenden Sie einen gesunden Menschenverstand.
Das erste Problem ist die Präzision. In vielen Sprachen haben Sie "float" und "double" (double steht für "double precision"), und in vielen Fällen haben Sie mit "float" eine Genauigkeit von etwa 7 Stellen, während Sie mit double eine Genauigkeit von 15 haben In Situationen, in denen Präzision ein Problem sein könnte, sind 15 Stellen viel besser als 7 Stellen. In vielen leicht problematischen Situationen bedeutet die Verwendung von "double", dass Sie damit durchkommen, und "float", dass Sie dies nicht tun. Nehmen wir an, die Marktkapitalisierung eines Unternehmens beträgt 700 Milliarden Dollar. Stellen Sie dies in float dar und das niedrigste Bit ist $ 65536. Stellen Sie es mit double dar, und das niedrigste Bit beträgt ungefähr 0,012 Cent. Wenn Sie also nicht wirklich genau wissen, was Sie tun, verwenden Sie double und nicht float.
Das zweite Problem ist eher eine Grundsatzfrage. Wenn Sie zwei verschiedene Berechnungen durchführen, die zum gleichen Ergebnis führen sollen, ist dies häufig nicht der Fall, da Rundungsfehler vorliegen. Zwei Ergebnisse, die gleich sein sollten, sind "fast gleich". Wenn zwei Ergebnisse nahe beieinander liegen, sind die tatsächlichen Werte möglicherweise gleich. Oder vielleicht auch nicht. Sie müssen dies berücksichtigen und Funktionen schreiben und verwenden, die besagen, dass "x definitiv größer als y ist" oder "x definitiv kleiner als y ist" oder "x und y könnten gleich sein".
Dieses Problem wird noch schlimmer, wenn Sie die Rundung verwenden, zum Beispiel "x auf die nächste ganze Zahl abrunden". Wenn Sie 120 * 0,05 multiplizieren, sollte das Ergebnis 6 sein, aber Sie erhalten "eine Zahl, die sehr nahe an 6 liegt". Wenn Sie dann "auf die nächste ganze Zahl abrunden", ist diese "Zahl sehr nahe an 6" möglicherweise "etwas kleiner als 6" und wird auf 5 gerundet. Es spielt keine Rolle, wie nahe Ihr Ergebnis bei 6 liegt, solange es weniger als 6 beträgt.
Und drittens sind einige Probleme schwierig . Das heißt, es gibt keine einfache und schnelle Regel. Wenn Ihr Compiler "long double" genauer unterstützt, können Sie "long double" verwenden und prüfen, ob dies einen Unterschied macht. Wenn es keinen Unterschied macht, sind Sie entweder in Ordnung oder Sie haben ein echtes kniffliges Problem. Wenn es die Art von Unterschied macht, die Sie erwarten würden (wie eine Änderung bei der 12. Dezimalstelle), dann sind Sie wahrscheinlich in Ordnung. Wenn es Ihre Ergebnisse wirklich ändert, haben Sie ein Problem. Bitte um Hilfe.
quelle
Die meisten Leute machen den Fehler, wenn sie doppelt sehen, dass sie BigDecimal schreien, obwohl sie das Problem gerade woanders hingestellt haben. Double gibt Vorzeichenbit: 1 Bit, Exponentenbreite: 11 Bit. Signifikante und Präzision: 53 Bits (52 explizit gespeichert). Aufgrund der Art des Double verlieren Sie mit zunehmender Größe des gesamten Intergers an relativer Genauigkeit. Zur Berechnung der relativen Genauigkeit, die wir hier verwenden, wird Folgendes angezeigt.
Relative Genauigkeit von double in der Berechnung verwenden wir das folgende Foluma 2 ^ E <= abs (X) <2 ^ (E + 1)
epsilon = 2 ^ (E-10)% Für einen 16-Bit-Float (halbe Genauigkeit)
Mit anderen Worten: Wenn Sie eine Genauigkeit von +/- 0,5 (oder 2 ^ -1) wünschen, kann die maximale Größe der Zahl 2 ^ 52 sein. Jeder größere Wert und der Abstand zwischen Gleitkommazahlen ist größer als 0,5.
Wenn Sie eine Genauigkeit von +/- 0,0005 (ungefähr 2 ^ -11) wünschen, ist die maximale Größe, die die Zahl sein kann, 2 ^ 42. Jeder größere Wert und der Abstand zwischen Gleitkommazahlen ist größer als 0,0005.
Eine bessere Antwort kann ich nicht geben. Der Benutzer muss herausfinden, welche Genauigkeit er bei der Durchführung der erforderlichen Berechnung und deren Einheitswert (Meter, Füße, Zoll, mm, cm) haben möchte. In den allermeisten Fällen reicht float für einfache Simulationen aus, abhängig von der Größe der Welt, die Sie simulieren möchten.
Obwohl es etwas zu sagen ist, wenn Sie nur eine Welt von 100 mal 100 Metern simulieren wollen, werden Sie eine Genauigkeit in der Größenordnung von 2 ^ -45 haben. Dies geht nicht einmal darauf ein, wie moderne FPUs innerhalb der CPU Berechnungen außerhalb der nativen Schriftgröße ausführen. Erst wenn die Berechnung abgeschlossen ist, werden sie (abhängig vom FPU-Rundungsmodus) auf die native Schriftgröße gerundet.
quelle