2010-11-15 11 views
16

Mam dużą ilość kodu Pythona, który próbuje obsługiwać liczby z dokładnością do czterech dziesiętnych i utknąłem z Pythona 2.4 z wielu powodów. Kod wykonuje bardzo uproszczoną matematykę (jest to kod zarządzania kredytem, ​​który pobiera lub dodaje kredyty w większości).Zło w pytonie dziesiętnym/zmiennoprzecinkowe

Wymusił użycie float i Decimal (MySQLdb zwraca obiekty dziesiętne dla typów SQL DECIMAL). Po kilku dziwnych błędach pochodzących z użycia, znalazłem podstawową przyczynę wszystkich, aby być kilkoma miejscami w kodzie, które unoszą się i dziesiętne są porównywane.

mam do przypadków tak:

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

Teraz mój strach jest, że nie może być w stanie złapać wszystkie takie przypadki w kodzie. (normalny programista będzie robił x> 0 zamiast x> dziesiętny ("0.0000") i bardzo trudno jest go uniknąć)

Wymyśliłem łatkę (inspirowaną poprawkami do pakietu dziesiętnego w 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 

po prostu zrobić to w bardzo wczesnym biblioteki ładowania i będzie to zmienić dziesiętną zachowanie pakietu, pozwalając na pływaka na dziesiętny konwersji przed porównaniem (aby uniknąć uderzenia domyślnego obiektu Pythona do obiektu porównanie).

W szczególności użyłem "str" ​​zamiast "repr", ponieważ naprawia niektóre przypadki zaokrąglania floata. Na przykład.

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

Teraz moje pytanie brzmi: Am I brakuje czegoś tutaj? Czy to jest dość bezpieczne? czy coś tu łamie? (mam na myśli autorów pakietu miał bardzo silne powody, aby uniknąć pływaków tak dużo)

Odpowiedz

4

Myślę, że chcesz raise NotImplementedError() zamiast return NotImplemented, aby rozpocząć.

To, co robisz, nazywa się "łataniem małp" i jest w porządku, o ile wiesz, co robisz, jesteś świadomy opadu i wszystko jest w porządku z tym opadem. Generalnie ograniczasz to do naprawienia błędu lub jakiejś innej zmiany, gdy wiesz, że zmiana zachowania jest nadal poprawna i zgodna wstecz.

W tym przypadku, ponieważ łatasz zajęcia, możesz zmienić zachowanie poza przypadkami, w których go używasz. Jeśli inna biblioteka używa dziesiętnego i w jakiś sposób polega na domyślnym zachowaniu, może powodować subtelne błędy. Kłopot w tym, że tak naprawdę nie wiesz, dopóki nie zbadasz swojego kodu, w tym żadnych zależności, i nie znajdziesz wszystkich stron z ogłoszeniami.

Zasadniczo - rób to na własne ryzyko.

Osobiście uważam, że poprawianie całego kodu, dodawanie testów i utrudnianie wykonywania niewłaściwych czynności (np. Stosowanie klas opakowania lub funkcji pomocniczych) jest dla mnie o wiele bardziej uspokajające. Innym podejściem byłoby oprzyrządowanie kodu za pomocą łaty, aby znaleźć wszystkie strony wywołań, a następnie wrócić i je naprawić.

Edytuj - Przypuszczam, że powinienem dodać, że prawdopodobnym powodem, dla którego unikali pływaków, nie są dokładne liczby, co jest ważne, jeśli masz do czynienia z pieniędzmi.

+1

Wystarczy zauważyć, że "return NotImplemented" pochodzi z samego pakietu decimal.py. Dwie dodane przeze mnie linie znajdują się między komentarzami. Zgadzam się z twoim podejściem, jednak w tej implementacji pyton pozwala na logicznie szalone porównania między obiektami, które, jak zakładamy, są liczbami. Hmm, innym pomysłem może być podniesienie błędu zamiast niejawnej konwersji, ale niezależnie od tego, myślę, że muszę coś zrobić ... –

+10

'return NotImplemented' jest poprawne i jest poprawne, [dokumentacja określona] (http: // docs .python.org/reference/datamodel.html # emulating-numeric-types) rzecz, którą należy zwrócić w przypadku nieobsługiwanego porównania. To pozwala pythonowi próbować znaleźć inny sposób robienia rzeczy. – aaronasterling

+0

+1 za użycie terminu "łatanie małp", które doprowadziło mnie do wikipedii, aby stwierdzić, że pochodzi ona od "łatania partyzantów", jak w wojnie partyzanckiej =). – Tommy

3

Istnieją bardzo dobre powody, aby unikać pływaków. W przypadku spławik nie można wiarygodnie wykonywać porównań, takich jak ==,>, < itd. Z powodu szumu zmiennoprzecinkowego. W każdej operacji zmiennoprzecinkowej gromadzisz hałas.Zaczyna się od bardzo małych cyfr pojawiających się na samym końcu, np. 1.000 ... 002, ale może w końcu się zgromadzić, takich jak 1.0000000453436.

Użycie str() może zadziałać, jeśli nie wykonujesz tak wielu obliczeń zmiennoprzecinkowych, ale jeśli wykonasz wiele obliczeń, szum zmiennoprzecinkowy będzie na tyle duży, że str() da ci zła odpowiedź.

W sumie, jeśli (1) nie zrobić wiele floating point obliczenia lub (2) nie trzeba robić porównań jak ==,>, < etc to może być ok .

Jeśli chcesz mieć pewność, usuń cały kod zmiennoprzecinkowy.

+0

Istnieją bardzo dobre powody, dla których należy unikać float ** w programach księgowych ** takich jak ten w pytaniu. Pływaki działają idealnie bez zarzutu w celu ich reprezentacji ** w przybliżeniu ** ilości. – dan04

+1

@Dan, tak, założeniem mojej odpowiedzi jest to, że nie można wykonać == z pływakami. Jeśli reprezentujesz przybliżone ilości, to nie używasz ==, ponieważ równość nie jest przybliżona. –