it-swarm-eu.dev

Was kann mit Programmiersprachen getan werden, um Gleitkomma-Fallstricke zu vermeiden?

Das Missverständnis der Gleitkomma-Arithmetik und ihrer Mängel ist eine Hauptursache für Überraschung und Verwirrung bei der Programmierung (berücksichtigen Sie die Anzahl der Fragen zum Stapelüberlauf im Zusammenhang mit "Zahlen, die nicht richtig addiert werden"). Angesichts der Tatsache, dass viele Programmierer die Auswirkungen noch nicht verstanden haben, kann es zu vielen subtilen Fehlern kommen (insbesondere bei Finanzsoftware). Was können Programmiersprachen tun, um ihre Fallstricke für diejenigen zu vermeiden, die mit den Konzepten nicht vertraut sind, und gleichzeitig ihre Geschwindigkeit bieten, wenn die Genauigkeit für diejenigen, die nicht kritisch sind, nicht kritisch ist? die Konzepte verstehen?

28
Adam Paynter

Sie sagen "speziell für Finanzsoftware", was eine meiner Lieblingsbeschwerden aufwirft: Geld ist kein Float, es ist ein int .

Klar, es sieht aus wie ein Schwimmer. Es hat dort einen Dezimalpunkt. Aber das liegt nur daran, dass Sie an Einheiten gewöhnt sind, die das Problem verwirren. Geld kommt immer in ganzzahligen Mengen. In Amerika sind es Cent. (In bestimmten Zusammenhängen denke ich es können Mühlen sein , aber ignoriere das vorerst.)

Wenn Sie also 1,23 Dollar sagen, sind das wirklich 123 Cent. Immer, immer, immer rechnen Sie in diesen Begriffen, und es wird Ihnen gut gehen. Weitere Informationen finden Sie unter:

Bei der direkten Beantwortung der Frage sollten Programmiersprachen nur einen Geldtyp als vernünftiges Grundelement enthalten.

update

Ok, ich hätte nur zweimal "immer" sagen sollen, anstatt dreimal. Geld ist in der Tat immer ein int; Wer anders denkt, kann mir gerne 0,3 Cent schicken und mir das Ergebnis auf Ihrem Kontoauszug zeigen. Aber wie Kommentatoren betonen, gibt es seltene Ausnahmen, wenn Sie Gleitkomma-Berechnungen für geldähnliche Zahlen durchführen müssen. Zum Beispiel bestimmte Arten von Preisen oder Zinsberechnungen. Selbst dann sollten diese wie Ausnahmen behandelt werden. Geld kommt als ganzzahlige Menge rein und raus. Je näher Ihr System daran kommt, desto vernünftiger wird es.

47
William Pietri

In vielen Fällen hilft es, einen Dezimaltyp zu unterstützen. Viele Sprachen haben einen Dezimaltyp, werden aber nicht ausreichend verwendet.

Es ist wichtig, die Annäherung zu verstehen, die beim Arbeiten mit der Darstellung reeller Zahlen auftritt. Die Verwendung von Dezimal- und Gleitkommatypen 9 * (1/9) != 1 ist eine korrekte Aussage. Bei Konstanten kann ein Optimierer die Berechnung so optimieren, dass sie korrekt ist.

Die Angabe eines ungefähren Operators würde helfen. Solche Vergleiche sind jedoch problematisch. Beachten Sie, dass 0,9999 Billionen Dollar ungefähr 1 Billion Dollar entsprechen. Könnten Sie bitte die Differenz auf mein Bankkonto einzahlen?

15
BillThor

Als ich zur Universität ging, wurde uns im ersten Jahr (im zweiten Studienjahr) in Informatik gesagt, was zu tun ist (dieser Kurs war auch eine Voraussetzung für die meisten naturwissenschaftlichen Kurse).

Ich erinnere mich an den Dozenten, der sagte: "Gleitkommazahlen sind Näherungswerte. Verwenden Sie ganzzahlige Typen für Geld. Verwenden Sie FORTRAN oder eine andere Sprache mit BCD-Nummern für eine genaue Berechnung." (und dann wies er auf die Annäherung hin, wobei er das klassische Beispiel von 0,2 verwendete, das im binären Gleitkomma nicht genau dargestellt werden kann). Dies tauchte auch in dieser Woche in den Laborübungen auf.

Gleiche Vorlesung: "Wenn Sie mehr Genauigkeit durch Gleitkomma erhalten möchten, sortieren Sie Ihre Begriffe. Addieren Sie kleine Zahlen, nicht zu großen Zahlen." Das blieb mir im Gedächtnis.

Vor ein paar Jahren hatte ich eine sphärische Geometrie, die sehr genau und trotzdem schnell sein musste. 80-Bit-Double auf PCs hat es nicht gekürzt, deshalb habe ich dem Programm einige Typen hinzugefügt, die Begriffe sortierten, bevor ich kommutative Operationen ausführte. Problem gelöst.

Bevor Sie sich über die Qualität der Gitarre beschweren, lernen Sie zu spielen.

Ich hatte vor vier Jahren einen Kollegen, der für JPL gearbeitet hatte. Er drückte seinen Unglauben aus, dass wir FORTRAN für einige Dinge benutzt haben. (Wir brauchten supergenaue numerische Simulationen, die offline berechnet wurden.) "Wir haben das ganze FORTRAN durch C++ ersetzt", sagte er stolz. Ich fragte mich nicht mehr, warum sie einen Planeten verpasst hatten.

9
Tim Williscroft

Warnung: Dem Gleitkommatyp System.Double fehlt die Genauigkeit für direkte Gleichheitstests.

double x = CalculateX();
if (x == 0.1)
{
    // ............
}

Ich glaube nicht, dass irgendetwas auf Sprachebene getan werden kann oder sollte.

8
ChaosPandion

Standardmäßig sollten Sprachen Rationals mit beliebiger Genauigkeit für nicht ganzzahlige Zahlen verwenden.

Wer optimieren muss, kann immer nach Floats fragen. Die Verwendung als Standard war in C und anderen Systemprogrammiersprachen sinnvoll, in den meisten heute gängigen Sprachen jedoch nicht.

7
Waquo

Die zwei größten Probleme mit Gleitkommazahlen sind:

  • inkonsistente Einheiten, die auf die Berechnungen angewendet werden (beachten Sie, dass dies in gleicher Weise auch die Ganzzahlarithmetik beeinflusst)
  • unverständnis, dass FP Zahlen eine Annäherung sind und wie man intelligent mit Rundungen umgeht.

Der erste Fehlertyp kann nur behoben werden, indem ein zusammengesetzter Typ bereitgestellt wird, der Wert- und Einheiteninformationen enthält. Zum Beispiel ein length oder area Wert, der die Einheit enthält (Meter oder Quadratmeter oder Fuß bzw. Quadratfuß). Andernfalls müssen Sie fleißig daran arbeiten, immer mit einer Art von Maßeinheit zu arbeiten und nur dann in eine andere umzurechnen, wenn wir die Antwort mit einem Menschen teilen.

Die zweite Art von Fehler ist ein konzeptioneller Fehler. Die Fehler manifestieren sich, wenn die Leute sie als absolute Zahlen betrachten. Dies wirkt sich auf Gleichheitsoperationen, kumulative Rundungsfehler usw. aus. Beispielsweise kann es richtig sein, dass für ein System zwei Messungen innerhalb einer bestimmten Fehlergrenze äquivalent sind. Das heißt, .999 und 1.001 sind ungefähr gleich 1.0, wenn Sie sich nicht für Unterschiede interessieren, die kleiner als +/- .1 sind. Allerdings sind nicht alle Systeme so nachsichtig.

Wenn eine Sprachstufe benötigt wird, würde ich sie Gleichheitsgenauigkeit nennen. In NUnit, JUnit und ähnlich konstruierten Testframeworks können Sie die Genauigkeit steuern, die als korrekt angesehen wird. Zum Beispiel:

Assert.That(.999, Is.EqualTo(1.001).Within(10).Percent);
// -- or --
Assert.That(.999, Is.EqualTo(1.001).Within(.1));

Wenn zum Beispiel C # oder Java geändert wurde, um einen Präzisionsoperator einzuschließen, könnte dies ungefähr so ​​aussehen:

if(.999 == 1.001 within .1) { /* do something */ }

Wenn Sie jedoch eine solche Funktion bereitstellen, müssen Sie auch den Fall berücksichtigen, in dem die Gleichheit gut ist, wenn die +/- Seiten nicht gleich sind. Zum Beispiel würde + 1/-10 zwei Zahlen als äquivalent betrachten, wenn eine von ihnen innerhalb von 1 mehr oder 10 weniger als die erste Zahl liegt. Um diesen Fall zu behandeln, müssen Sie möglicherweise auch ein Schlüsselwort range hinzufügen:

if(.999 == 1.001 within range(.001, -.1)) { /* do something */ }
4
Berin Loritsch

Eine Sache, die ich gerne sehen würde, wäre die Erkenntnis, dass double zu float als eine sich erweiternde Konvertierung angesehen werden sollte, während float zu double sich verengt ( *). Das mag kontraintuitiv erscheinen, aber überlegen Sie, was die Typen tatsächlich bedeuten:

  • 0,1f bedeutet "13.421.773,5/134.217.728, plus oder minus 1/268,435,456 oder so".
  • 0,1 bedeutet tatsächlich 3.602.879.701.896.397/36.028.797.018.963.968 plus oder minus 1/72.057.594.037.927.936 oder so "

Wenn man ein double hat, das die beste Darstellung der Menge "ein Zehntel" enthält und sie in float umwandelt, ist das Ergebnis "13.421.773,5/134.217.728, plus oder minus 1/268,435,456 oder also ", was eine korrekte Beschreibung des Wertes ist.

Wenn man dagegen ein float hat, das die beste Darstellung der Menge "ein Zehntel" enthält und diese in double umwandelt, ist das Ergebnis "13.421.773,5/134.217.728, plus oder minus 1/72.057.594.037.927.936 oder so "- ein Maß an impliziter Genauigkeit, das um einen Faktor von über 53 Millionen falsch ist.

Obwohl der IEEE-744-Standard die Durchführung von Gleitkomma-Berechnungen erfordert als ob Jede Gleitkommazahl stellt die genaue numerische Größe genau in der Mitte ihres Bereichs dar. Dies sollte nicht bedeuten, dass Gleitkommawerte tatsächlich diese genauen numerischen Größen darstellen. Die Anforderung, dass die Werte als Mittelpunkt ihres Bereichs angenommen werden müssen, beruht vielmehr auf drei Tatsachen: (1) Berechnungen müssen so durchgeführt werden, als ob die Operanden bestimmte genaue Werte haben; (2) konsistente und dokumentierte Annahmen sind hilfreicher als inkonsistente oder nicht dokumentierte; (3) Wenn man eine konsistente Annahme treffen will, ist keine andere konsistente Annahme besser als die Annahme, dass eine Größe das Zentrum ihres Bereichs darstellt.

Ich erinnere mich übrigens, dass vor ungefähr 25 Jahren jemand ein numerisches Paket für C entwickelt hat, das "Bereichstypen" verwendet, die jeweils aus einem Paar 128-Bit-Floats bestehen. Alle Berechnungen würden so durchgeführt, dass für jedes Ergebnis der minimal und maximal mögliche Wert berechnet wird. Wenn man eine große lange iterative Berechnung durchführte und einen Wert von [12.53401391134 12.53902812673] ergab, konnte man sicher sein, dass viele Stellen der Genauigkeit durch Rundungsfehler verloren gingen, das Ergebnis jedoch vernünftigerweise als 12,54 ausgedrückt werden konnte (und es war nicht '). t wirklich 12.9 oder 53.2). Ich bin überrascht, dass ich in keiner der gängigen Sprachen Unterstützung für solche Typen gesehen habe, zumal sie gut zu mathematischen Einheiten passen, die mehrere Werte parallel bearbeiten können.

(*) In der Praxis ist es oft hilfreich, Werte mit doppelter Genauigkeit zu verwenden, um Zwischenberechnungen durchzuführen, wenn mit Zahlen mit einfacher Genauigkeit gearbeitet wird. Daher kann es ärgerlich sein, für alle derartigen Operationen einen Typecast zu verwenden. Sprachen könnten helfen, indem sie einen "Fuzzy Double" -Typ haben, der Berechnungen als Double ausführt und frei in und aus Single umgewandelt werden kann. Dies wäre besonders hilfreich, wenn Funktionen, die Parameter vom Typ double annehmen und double zurückgeben, so markiert werden könnten, dass sie automatisch eine Überladung erzeugen, die stattdessen "Fuzzy Double" akzeptiert und zurückgibt.

3
supercat

Eine Sache, die Sprachen tun könnten - entfernen Sie den Gleichheitsvergleich aus anderen Gleitkommatypen als einem direkten Vergleich mit den NAN-Werten.

Gleichheitstests würden nur als Funktionsaufruf existieren, der die beiden Werte und ein Delta annimmt, oder für Sprachen wie C #, die es Typen ermöglichen, Methoden ein EqualsTo zu haben, das den anderen Wert und das Delta annimmt.

3
Loren Pechtel

Ich finde es seltsam, dass niemand auf den rationalen Zahlentrick der LISP-Familie hingewiesen hat.

Im Ernst, öffnen Sie sbcl und machen Sie Folgendes: (+ 1 3) Und Sie erhalten 4. Wenn Sie*( 3 2) erhalten, erhalten Sie 6. Versuchen Sie nun (/ 5 3) Und Sie erhalten 5/3 oder 5 Drittel.

Das sollte in manchen Situationen etwas helfen, oder?

3
Haakon Løtveit

Was können Programmiersprachen? Ich weiß nicht, ob es eine Antwort auf diese Frage gibt, da alles, was der Compiler/Interpreter im Namen des Programmierers tut, um ihm das Leben zu erleichtern, normalerweise der Leistung, Klarheit und Lesbarkeit entgegenwirkt. Ich denke, dass sowohl der C++ - Weg (zahlen Sie nur für das, was Sie brauchen) als auch der Perl-Weg (Prinzip der geringsten Überraschung) gültig sind, aber es hängt von der Anwendung ab.

Programmierer müssen immer noch mit der Sprache arbeiten und verstehen, wie sie mit Gleitkommazahlen umgeht, denn wenn sie dies nicht tun, werden sie Annahmen treffen, und eines Tages wird das beschriebene Verhalten nicht mehr mit ihren Annahmen übereinstimmen.

Meine Meinung zu dem, was der Programmierer wissen muss:

  • Welche Gleitkommatypen sind im System und in der Sprache verfügbar?
  • Welcher Typ wird benötigt?
  • Wie man die Absichten ausdrückt, welcher Typ im Code benötigt wird
  • So nutzen Sie jede automatische Typwerbung richtig, um Klarheit und Effizienz bei gleichzeitiger Wahrung der Korrektheit in Einklang zu bringen
3
John

Ich bin damit einverstanden, dass es auf Sprachebene nichts zu tun gibt. Programmierer müssen verstehen, dass Computer diskret und begrenzt sind und dass viele der darin dargestellten mathematischen Konzepte nur Annäherungen sind.

Egal Gleitkomma. Man muss verstehen, dass die Hälfte der Bitmuster für negative Zahlen verwendet wird und dass 2 ^ 64 tatsächlich ziemlich klein ist, um typische Probleme mit der Ganzzahlarithmetik zu vermeiden.

3
Apalala

Was können Programmiersprachen tun, um [Gleitkomma-] Fallstricke zu vermeiden ...?

Verwenden Sie sinnvolle Standardeinstellungen, z. eingebaute Unterstützung für Decmials.

Groovy macht das ganz gut, obwohl Sie mit ein wenig Aufwand immer noch Code schreiben können, um Gleitkomma-Ungenauigkeiten einzuführen.

3
Armand

Wenn mehr Programmiersprachen eine Seite aus Datenbanken entnehmen und es Entwicklern ermöglichen würden, die Länge und Genauigkeit ihrer numerischen Datentypen anzugeben, könnten sie die Wahrscheinlichkeit von Gleitkommafehlern erheblich verringern. Wenn eine Sprache es einem Entwickler erlaubt, eine Variable als Float (2) zu deklarieren, was darauf hinweist, dass er eine Gleitkommazahl mit zwei Dezimalstellen Genauigkeit benötigt, kann er mathematische Operationen viel sicherer ausführen. Wenn dies der Fall wäre, indem die Variable intern als Ganzzahl dargestellt und vor dem Anzeigen des Werts durch 100 dividiert wird, könnte die Geschwindigkeit durch Verwendung der schnelleren ganzzahligen arithmetischen Pfade verbessert werden. Die Semantik eines Floats (2) würde es Entwicklern auch ermöglichen, die ständige Notwendigkeit zu vermeiden, Daten vor der Ausgabe zu runden, da ein Float (2) Daten von Natur aus auf zwei Dezimalstellen runden würde.

Natürlich müssen Sie einem Entwickler erlauben, nach einem Gleitkommawert mit maximaler Genauigkeit zu fragen, wenn der Entwickler diese Genauigkeit benötigt. Und Sie würden Probleme einführen, bei denen leicht unterschiedliche Ausdrücke derselben mathematischen Operation aufgrund von Zwischenrundungsoperationen möglicherweise unterschiedliche Ergebnisse liefern, wenn Entwickler ihre Variablen nicht präzise genug ausführen. Aber zumindest in der Datenbankwelt scheint das keine allzu große Sache zu sein. Die meisten Menschen führen nicht die Art von wissenschaftlichen Berechnungen durch, die viel Präzision bei Zwischenergebnissen erfordern.

2
Justin Cave

Wie andere Antworten festgestellt haben, besteht die einzige Möglichkeit, Gleitkomma-Fallstricke in Finanzsoftware zu vermeiden, darin, sie dort nicht zu verwenden. Dies kann tatsächlich möglich sein - wenn Sie eine gut gestaltete Bibliothek bereitstellen, die sich der Finanzmathematik widmet .

Funktionen zum Importieren von Gleitkommaschätzungen sollten eindeutig als solche gekennzeichnet und mit Parametern versehen sein, die für diese Operation geeignet sind, z.

Finance.importEstimate(float value, Finance roundingStep)

Der einzige wirkliche Weg, um Gleitkomma-Fallstricke im Allgemeinen zu vermeiden, ist Bildung - Programmierer müssen etwas lesen und verstehen wie Was jeder Programmierer über Gleitkomma-Arithmetik wissen sollte .

Ein paar Dinge, die helfen könnten:

  • Ich werde diejenigen unterstützen, die fragen: "Warum ist eine exakte Gleichheitsprüfung für Gleitkomma überhaupt legal?"
  • Verwenden Sie stattdessen eine isNear() Funktion.
  • Bereitstellung und Förderung der Verwendung von Gleitkomma-Akkumulatorobjekten (die Sequenzen von Gleitkommawerten stabiler hinzufügen, als sie alle einfach zu einer regulären Gleitkommavariablen hinzuzufügen).
1
comingstorm
  • sprachen unterstützen Dezimalzeichen. Natürlich löst dies das Problem nicht wirklich, dennoch haben Sie keine genaue und endliche Darstellung von zum Beispiel ⅓;
  • einige DBs und Frameworks unterstützen Money Type. Hierbei wird im Grunde die Anzahl der Cent als Ganzzahl gespeichert.
  • es gibt einige Bibliotheken zur Unterstützung rationaler Zahlen. das löst das Problem von ⅓, löst aber nicht das Problem von zum Beispiel √2;

Diese oben genannten sind in einigen Fällen anwendbar, aber keine allgemeine Lösung für den Umgang mit Float-Werten. Die wirkliche Lösung besteht darin, das Problem zu verstehen und zu lernen, wie man damit umgeht. Wenn Sie Gleitkommaberechnungen verwenden, sollten Sie immer überprüfen, ob Ihre Algorithmen numerisch stabil sind. Es gibt ein riesiges Gebiet der Mathematik/Informatik, das sich auf das Problem bezieht. Es heißt Numerische Analyse.

1
vartec