2010-11-15 9 views
16

Ich habe eine große Menge an Python-Code, der Zahlen mit 4 Dezimalstellen zu behandeln versucht und ich bin aus vielen Gründen mit Python 2.4 stecken. Der Code tut sehr vereinfachende Mathematik (es ist ein Kredit-Management-Code, der meist Kredite nimmt oder hinzufügt)Böse in der Python Dezimal/float

Es hat die Verwendung von float und Dezimal vermengt (MySQLdb gibt Dezimalobjekte für SQL DECIMAL-Typen zurück). Nach einigen seltsamen Fehlern, die von der Verwendung herrühren, habe ich die Ursache dafür gefunden, dass einige Stellen im Code floaten und Dezimalzahlen verglichen werden.

Ich habe auf Fälle wie folgt aus:

>>> from decimal import Decimal 
>>> max(Decimal('0.06'), 0.6) 
Decimal("0.06") 

Nun meine Angst ist, dass ich nicht in der Lage sein könnte, alle diese Fälle in den Code zu fangen. (ein normaler Programmierer macht weiterhin x> 0 anstelle von x> Dezimal ('0.0000') und es ist sehr schwer zu vermeiden)

Ich habe einen Patch entwickelt (inspiriert von Verbesserungen des dezimalen Pakets in Python 2.7) .

import decimal 
def _convert_other(other): 
    """Convert other to Decimal. 

    Verifies that it's ok to use in an implicit construction. 
    """ 
    if isinstance(other, Decimal): 
     return other 
    if isinstance(other, (int, long)): 
     return Decimal(other) 
    # Our small patch begins 
    if isinstance(other, float): 
     return Decimal(str(other)) 
    # Our small patch ends 
    return NotImplemented 
decimal._convert_other = _convert_other 

Ich mache es nur in einer sehr frühen Laden Bibliothek, und es wird das Dezimalsystem Paket Verhalten für Schwimmer zu Dezimal Umwandlung vor Vergleichen, indem ändern (zur Vermeidung von Python des Standard-Objekt schlägt Vergleich zum Objekt).

Ich verwendete speziell "str" ​​anstelle von "repr", da es einige Rundungsfälle von Floats behebt. Z.B.

>>> Decimal(str(0.6)) 
Decimal("0.6") 
>>> Decimal(repr(0.6)) 
Decimal("0.59999999999999998") 

Nun meine Frage ist: Fehle ich hier irgendetwas? Ist das ziemlich sicher? oder breche ich hier etwas? (Ich denke, die Autoren des Pakets hatten sehr starke Gründe, schwimmt so viel zu vermeiden)

Antwort

4

Ich denke, Sie wollen raise NotImplementedError() anstelle von return NotImplemented, um zu starten.

Was Sie tun, ist "Affe Patching", und es ist OK zu tun, solange Sie wissen, was Sie tun, sind sich der Auswirkungen bewusst, und sind OK mit diesem Fallout. Im Allgemeinen beschränken Sie dies auf die Behebung eines Fehlers oder einer anderen Änderung, bei der Sie wissen, dass Sie das Verhalten noch richtig und abwärtskompatibel sind.

In diesem Fall können Sie, weil Sie eine Klasse patchen, das Verhalten außerhalb der Fälle ändern, in denen Sie sie verwenden. Wenn eine andere Bibliothek dezimal verwendet und sich irgendwie auf das Standardverhalten verlässt, kann dies zu subtilen Fehlern führen. Das Problem ist, dass Sie nicht wirklich wissen, es sei denn, Sie auditieren alle Ihren Code, einschließlich Abhängigkeiten, und finden Sie alle Call-Sites.

Grundsätzlich - tun Sie es auf eigene Gefahr.

Persönlich finde ich es beruhigender, meinen gesamten Code zu reparieren, Tests hinzuzufügen und es schwerer zu machen, das Falsche zu tun (z. B. Wrapperklassen oder Hilfsfunktionen). Ein anderer Ansatz wäre, Ihren Code mit Ihrem Patch zu instrumentieren, um alle Call-Sites zu finden, und dann zurückzugehen und sie zu beheben.

Bearbeiten - Ich denke, ich sollte hinzufügen, dass der wahrscheinliche Grund, warum sie Floats vermieden werden, Floats sind, kann nicht alle Zahlen genau darstellen, was wichtig ist, wenn Sie mit Geld zu tun haben.

+1

Nur eine Anmerkung, dass die "Rückgabe NotImplemented" aus dem decimal.py-Paket selbst stammt. Die zwei Zeilen, die ich hinzugefügt habe, sind zwischen den Kommentaren. Ich stimme Ihrer Herangehensweise zu, jedoch erlaubt Python in dieser Implementierung logisch wahnsinnige Vergleiche zwischen Objekten, von denen wir annehmen, dass sie beide Zahlen sind. Hmm, eine andere Idee könnte sein, einen Fehler anstelle einer impliziten Konvertierung zu erzeugen, aber egal, ich denke, ich muss etwas tun ... –

+10

'return NotImplemented' ist korrekt und ist das richtige, [Dokumentation angegeben] (http: // docs .python.org/reference/datamodel.html # emulating-numeric-types), die für einen nicht unterstützten Vergleich zurückgegeben werden soll. Es erlaubt Python, einen anderen Weg zu finden, Dinge zu tun. – aaronasterling

+0

+1 für die Verwendung des Begriffs "Affe Patching", die mich zu Wikipedia führte der Begriff, um zu finden, kommt von "Guerilla Patchen", wie in Guerillakrieg = =). – Tommy

3

Es gibt sehr gute Gründe, Floats zu vermeiden. Mit Gleitkommazahlen können Sie Vergleiche wie ==,>, < usw. wegen Fließkomma-Rauschen nicht zuverlässig durchführen. Bei jeder Gleitkommaoperation akkumulieren Sie Rauschen.Es beginnt mit sehr kleinen Ziffern, die ganz am Ende erscheinen, z. B. 1.000 ... 002, aber es kann sich schließlich ansammeln, wie etwa 1.0000000453436. Wenn Sie nicht so viele Fließkommaberechnungen durchführen, kann die Verwendung von str() für Sie funktionieren, aber wenn Sie viele Berechnungen durchführen, wird das Fließkomma - Rauschen schließlich groß genug sein, dass str() Ihnen die Zahl geben wird falsche Antwort.

In Summe, wenn (1) Sie tun nicht, dass viele Gleitkomma-Berechnungen oder (2) Sie tun müssen, nicht Vergleiche wie ==,>, < etc dann könnten Sie in Ordnung sein .

Wenn Sie sicher sein wollen, dann entfernen Sie alle Fließkomma-Code.

+0

Es gibt sehr gute Gründe, Floats ** in Buchhaltungsprogrammen ** wie dem in der Frage zu vermeiden. Floats funktionieren perfekt für ihren beabsichtigten Zweck, ** ungefähre ** Mengen darzustellen. – dan04

+1

@Dan, ja, die Prämisse meiner Antwort ist, dass Sie == mit Floats nicht tun können. Wenn Sie ungefähre Größen darstellen, verwenden Sie nicht ==, da die Gleichheit keine ungefähre Größe ist. –

Verwandte Themen