it-swarm.com.de

Lösungen für Gleitkomma-Rundungsfehler

Beim Erstellen einer Anwendung, die sich mit vielen mathematischen Berechnungen befasst, bin ich auf das Problem gestoßen, dass bestimmte Zahlen Rundungsfehler verursachen.

Während ich verstehe, dass Gleitkomma ist nicht exakt , ist das Problem , wie ich mit exakten Zahlen umgehe, um sicherzustellen, dass wann Berechnungen werden auf ihnen durchgeführt. Gleitkomma-Rundungen verursachen keine Probleme?

18
JNL

Es gibt drei grundlegende Ansätze zum Erstellen alternativer numerischer Typen, die frei von Gleitkomma-Rundungen sind. Das gemeinsame Thema bei diesen ist, dass sie stattdessen ganzzahlige Mathematik auf verschiedene Arten verwenden.

Rationals

Stellen Sie die Zahl als Ganzes und die rationale Zahl mit einem Zähler und einem Nenner dar. Die Nummer 15.589 würde dargestellt werden als w: 15; n: 589; d:1000.

Bei Addition zu 0,25 (das ist w: 0; n: 1; d: 4) beinhaltet dies die Berechnung des LCM und das anschließende Addieren der beiden Zahlen. Dies funktioniert in vielen Situationen gut, kann jedoch zu sehr großen Zahlen führen, wenn Sie mit vielen rationalen Zahlen arbeiten, die relativ prim zueinander stehen.

Fixpunkt

Sie haben den ganzen Teil und den Dezimalteil. Alle Zahlen sind auf diese Genauigkeit gerundet (es gibt dieses Wort - aber Sie wissen, wo es ist). Zum Beispiel könnten Sie einen festen Punkt mit 3 Dezimalstellen haben. 15.589 + 0.250 wird zum Hinzufügen von 589 + 250 % 1000 für den Dezimalteil (und dann alle Übertragungen auf 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 so angeben, dass sie genauer ist als erforderlich (Sie messen nur mit 3 Dezimalstellen, machen Sie sie also fest 4).

Gleitender Festpunkt

Speichern Sie einen Wert und die Genauigkeit. 15.589 wird gespeichert als 15589 für den Wert und 3 für die Präzision, während 0.25 wird gespeichert als 25 und 2. Dies kann mit beliebiger Genauigkeit umgehen. Ich glaube , dass dies die Interna von Javas BigDecimal verwendet (haben es in letzter Zeit nicht angeschaut). Irgendwann möchten Sie es wieder aus diesem Format entfernen und anzeigen - und das kann eine Rundung beinhalten (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 schreiben. Stellen Sie beim Schreiben Ihrer eigenen sicher, dass Sie sie einem Unit-Test unterziehen und sicherstellen, dass Sie die Mathematik korrekt ausführen.

22
user40980

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 rechne ich mit nicht ganzzahligen Werten ohne Gleitkommavariablen?" Die Antwort lautet Datentypen mit beliebiger 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 stehen Bibliotheken mit beliebiger Genauigkeit zur Verfügung.

10
Mason Wheeler

Gleitkomma-Arithmetik ist normalerweise ziemlich genau (15 Dezimalstellen für ein double) und ziemlich flexibel. Die Probleme treten auf, wenn Sie rechnen, wodurch sich die Genauigkeit der Ziffern erheblich verringert. Hier sind einige Beispiele:

  • Abbruch bei Subtraktion: 1234567890.12345 - 1234567890.12300, Das Ergebnis 0.0045 Hat nur zwei Dezimalstellen Genauigkeit. Dies tritt immer dann auf, wenn Sie zwei Zahlen ähnlicher Größe subtrahieren.

  • Präzisionsschlucken: 1234567890.12345 + 0.123456789012345 Wird zu 1234567890.24691 Ausgewertet, die letzten zehn Ziffern des zweiten Operanden gehen verloren.

  • Multiplikationen: Wenn Sie zwei 15-stellige Zahlen multiplizieren, enthält das Ergebnis 30 Stellen, die gespeichert werden müssen. Sie können sie jedoch nicht speichern, sodass die letzten 15 Bits verloren gehen. Dies ist besonders lästig, wenn es mit einer sqrt() kombiniert wird (wie in sqrt(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 vermieden werden. Wenn Sie beispielsweise einen Wert in einer Schleife immer wieder erhöhen müssen, vermeiden Sie Folgendes:

for(double f = f0; f < f1; f += df) {

Nach einigen Iterationen verschluckt das größere f einen Teil der Genauigkeit von df. Schlimmer noch, die Fehler summieren sich und führen zu der kontraintuitiven Situation, dass ein kleineres df zu schlechteren Gesamtergebnissen führen kann. Schreiben Sie dies besser:

for(int i = 0; i < (f1 - f0)/df; i++) {
    double f = f0 + i*df;

Da Sie die Inkremente in einer einzigen Multiplikation kombinieren, ist das resultierende f auf 15 Dezimalstellen genau.

Dies ist nur ein Beispiel. Es gibt andere Möglichkeiten, um Präzisionsverluste aus anderen Gründen zu vermeiden. Aber es hilft schon sehr, ü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.

So stellen Sie sicher, dass Sie keine Probleme haben: Erfahren Sie mehr über Gleitkomma-Arithmetikprobleme oder 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" (doppelt für "doppelte Genauigkeit"), und in vielen Fällen erhalten Sie mit "float" eine Genauigkeit von etwa 7 Stellen, während "double" 15 ergibt. Der gesunde Menschenverstand ist, dass Sie eine haben In einer Situation, in der Präzision ein Problem sein könnte, sind 15 Stellen sehr viel besser als 7 Stellen. In vielen leicht problematischen Situationen bedeutet die Verwendung von "double", dass Sie damit durchkommen, und "float" bedeutet, 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 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 das gleiche Ergebnis liefern sollen, liegt dies häufig nicht an Rundungsfehlern. Zwei Ergebnisse, die gleich sein sollten, sind "fast gleich". Wenn zwei Ergebnisse nahe beieinander liegen, sind die tatsächlichen Werte möglicherweise gleich. Oder sie könnten es nicht sein. Sie müssen dies berücksichtigen und sollten Funktionen schreiben und verwenden, die sagen "x ist definitiv größer als y" oder "x ist definitiv kleiner als y" oder "x und y könnten gleich sein".

Dieses Problem wird noch schlimmer, wenn Sie die Rundung verwenden, z. B. "x auf die nächste Ganzzahl 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 Ganzzahl abrunden", könnte diese "Zahl sehr nahe an 6" "etwas kleiner als 6" sein und auf 5 gerundet werden. Beachten Sie, dass es keine Rolle spielt, wie genau Sie sind. Egal wie nahe an 6 ist Ihr Ergebnis, solange es weniger als 6 ist.

Und drittens sind einige Probleme schwierig. Das heißt, es gibt keine schnelle und einfache 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 wirklich kniffliges Problem. Wenn es den Unterschied macht, den Sie erwarten würden (wie eine Änderung bei der 12. Dezimalstelle), sind Sie wahrscheinlich in Ordnung. Wenn es Ihre Ergebnisse wirklich ändert, haben Sie ein Problem. Bitten Sie um Hilfe.

2
gnasher729

Die meisten Leute machen den Fehler, wenn sie doppelt sehen, dass sie BigDecimal schreien, obwohl sie das Problem gerade an einen anderen Ort verschoben haben. Double ergibt Vorzeichenbit: 1 Bit, Exponentenbreite: 11 Bit. Signifikante Genauigkeit: 53 Bit (52 explizit gespeichert). Aufgrund der Natur des Doppelten verlieren Sie die relative Genauigkeit, je größer der gesamte Interger ist. Um die relative Genauigkeit zu berechnen, verwenden wir hier unten.

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)

 Accuracy Power | Accuracy -/+| Maximum Power | Max Interger Value
 2^-1           | 0.5         | 2^51          | 2.2518E+15
 2^-5           | 0.03125     | 2^47          | 1.40737E+14
 2^-10          | 0.000976563 | 2^42          | 4.39805E+12
 2^-15          | 3.05176E-05 | 2^37          | 1.37439E+11
 2^-20          | 9.53674E-07 | 2^32          | 4294967296
 2^-25          | 2.98023E-08 | 2^27          | 134217728
 2^-30          | 9.31323E-10 | 2^22          | 4194304
 2^-35          | 2.91038E-11 | 2^17          | 131072
 2^-40          | 9.09495E-13 | 2^12          | 4096
 2^-45          | 2.84217E-14 | 2^7           | 128
 2^-50          | 8.88178E-16 | 2^2           | 4

Mit anderen Worten Wenn Sie eine Genauigkeit von +/- 0,5 (oder 2 ^ -1) wünschen, kann die Zahl maximal 2 ^ 52 sein. Jeder größere als dieser und der Abstand zwischen Gleitkommazahlen ist größer als 0,5.

Wenn Sie eine Genauigkeit von +/- 0,0005 (ca. 2 ^ -11) wünschen, kann die Zahl maximal 2 ^ 42 sein. Jeder größere Wert und der Abstand zwischen Gleitkommazahlen ist größer als 0,0005.

Ich kann wirklich keine bessere Antwort geben. Der Benutzer muss herausfinden, welche Präzision 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 irgendwo in der Größenordnung von 2 ^ -45 sein. Dies geht nicht einmal darauf ein, wie moderne FPUs in CPUs Berechnungen außerhalb der nativen Typgröße durchführen, und erst nach Abschluss der Berechnung werden sie (abhängig vom FPU-Rundungsmodus) auf die native Typgröße gerundet.

0
Chad