2010-11-15 3 views
16

У меня есть большое количество кода на Python, который пытается обрабатывать числа с 4-мя десятичной точностью, и я застрял с python 2.4 по многим причинам. Код делает очень упрощенную математику (ее код управления кредитами, который принимает или добавляет кредиты в основном)Зло в python decimal/float

Он имеет смешанное использование float и Decimal (MySQLdb возвращает десятичные объекты для типов SQL DECIMAL). После нескольких странных ошибок, возникающих из-за использования, я нашел основную причину для нескольких мест в коде, который плавает, и Decimals сравниваются.

меня к случаям, как это:

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

Теперь мой страх, что я не мог бы быть в состоянии охватить все такие случаи в коде. (нормальный программист будет продолжать делать x> 0 вместо x> Decimal ('0.0000'), и этого очень трудно избежать)

Я придумал патч (вдохновленный усовершенствованиями десятичного пакета в 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 

Я просто сделать это в самом начале загрузки библиотеки и изменит десятичное поведение пакета, позволяя плавать в десятичное преобразование до сравнения (чтобы избежать удара объекта питона по умолчанию для объекта сравнения).

Я специально использовал «str» вместо «repr», так как он исправляет некоторые закругляющие случаи флота. Например.

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

Теперь мой вопрос: Я пропускаю здесь что-нибудь? Это достаточно безопасно? или я что-то сломаю? (Я думаю, что у авторов пакета были очень веские причины, чтобы избежать поплавков)

ответ

4

Я думаю, вы хотите, чтобы raise NotImplementedError() вместо return NotImplemented, для начала.

Что вы делаете, называется «обезглавливание обезьян», и это нормально, если вы знаете, что вы делаете, знают о выпадении, и все в порядке с этим выпадением. Как правило, вы ограничиваете это исправлением ошибки или другим изменением, когда вы знаете, что вы изменяете поведение, по-прежнему правильное и обратно совместимое.

В этом случае, поскольку вы исправляете класс, вы можете изменить поведение за пределами случаев, когда вы его используете. Если в другой библиотеке используется десятичное число и каким-то образом зависит от поведения по умолчанию, это может вызвать тонкие ошибки. Проблема в том, что вы действительно не знаете, если вы не проверите весь код, включая любые зависимости, и найдите все сайты вызовов.

В основном - сделайте это на свой страх и риск.

Лично я считаю более обнадеживающим, чтобы исправить весь мой код, добавить тесты и сделать сложнее делать неправильную вещь (например, использовать классы-оболочки или вспомогательные функции). Другим подходом было бы привязать ваш код к вашему патчу, чтобы найти все сайты вызовов, а затем вернуться и исправить их.

Редактировать - Я думаю, я должен добавить, что вероятной причиной, по которой они избегали плавающих поплавков, являются поплавки, которые не могут точно представлять все числа, что важно, если вы имеете дело с деньгами.

+1

Просто обратите внимание, что «return NotImplemented» - это сам пакет decimal.py. Две строки, которые я добавил, находятся между комментариями. Однако я согласен с вашим подходом в этой реализации, python позволяет логически сумасшедшие сравнения между объектами, которые мы предполагаем, являются обоими числами. Хм, другая идея может заключаться в том, чтобы поднять ошибку вместо неявного преобразования, но независимо от того, я думаю, что мне нужно что-то сделать ... –

+10

'return NotImplemented' является правильным и является правильным, [указанная документация] (http: // docs .python.org/reference/datamodel.html # emulating-numeric-types), чтобы вернуться для неподдерживаемого сравнения. Это позволяет питону пытаться найти другой способ сделать что-то. – aaronasterling

+0

+1 для использования термина «патч обезьяны», который привел меня к википедии этого термина, чтобы найти его из «партизанских патчей», как в партизанской войне =). – Tommy

3

Есть очень веские причины, чтобы избежать поплавков. С поплавками вы не можете надежно выполнять сравнения, такие как ==,>, < и т. Д. Из-за шума с плавающей точкой. При любой операции с плавающей точкой вы накапливаете шум.Он начинается с очень маленьких цифр, появляющихся в самом конце, например, 1.000 ... 002, но в конечном итоге он может накапливаться, например, 1.0000000453436.

Использование str() может работать для вас, если вы не выполняете столько вычислений с плавающей запятой, но если вы выполняете множество вычислений, то с плавающей точкой в ​​конечном итоге будет достаточно большой, чтобы str() предоставила вам неверный ответ.

В общем, если (1) вы не делаете, что многие вычисления с плавающей точкой, или (2) вам не нужно делать сравнения как ==,>, < и т.д. , то вы можете быть в порядке ,

Если вы хотите быть уверены, то удалите все коды с плавающей точкой.

+0

Есть очень веские причины, чтобы избежать поплавков ** в бухгалтерских программах **, как в вопросе. Поплавки отлично работают с целью их представления ** приблизительных ** величин. – dan04

+1

@ Дана, да, предпосылка моего ответа заключается в том, что вы не можете делать == с поплавками. Если вы представляете приблизительные величины, то вы не используете ==, поскольку равенство не является приблизительным. –