Зло в python decimal/float
У меня есть большой код python, который пытается обрабатывать числа с 4-мя десятичной точностью, и я застрял с python 2.4 по многим причинам. Код делает очень упрощенную математику (ее код управления кредитами, который берет или добавляет кредиты в основном)
В нем используется смешанное использование 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 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
Я просто делаю это в очень ранней загрузочной библиотеке, и он изменит поведение десятичного пакета, разрешив преобразование с плавающей запятой в Decimal перед сравнением (чтобы избежать удара по объекту по умолчанию для объекта python).
Я специально использовал "str" вместо "repr", поскольку он исправляет некоторые случаи округления с плавающей запятой. Например.
>>> Decimal(str(0.6))
Decimal("0.6")
>>> Decimal(repr(0.6))
Decimal("0.59999999999999998")
Теперь мой вопрос:
Я что-то пропустил? Это достаточно безопасно? или я что-то сломаю?
(Я думаю, что у авторов пакета были очень веские причины, чтобы избежать так много плавать)
Ответы
Ответ 1
Я думаю, вы хотите raise NotImplementedError()
вместо return NotImplemented
, чтобы начать.
То, что вы делаете, называется "исправление обезьян", и это нормально делать, пока вы знаете, что делаете, знают о выпадении, и все в порядке с этим выпадением. Как правило, вы ограничиваете это исправлением ошибки или другим изменением, когда вы знаете, что вы изменяете поведение, по-прежнему правильное и обратно совместимое.
В этом случае, поскольку вы исправляете класс, вы можете изменить поведение за пределами случаев, когда вы его используете. Если в другой библиотеке используется десятичное число и каким-то образом зависит от поведения по умолчанию, это может вызвать тонкие ошибки. Проблема в том, что вы действительно не знаете, если вы не проверите весь свой код, включая любые зависимости, и найдите все сайты вызовов.
В принципе - делайте это на свой страх и риск.
Лично я нахожу более обнадеживающим, чтобы исправить весь мой код, добавить тесты и сделать сложнее делать неправильную вещь (например, использовать классы-оболочки или вспомогательные функции). Другим подходом было бы привязать ваш код к вашему патчу, чтобы найти все сайты вызовов, а затем вернуться и исправить их.
Изменить. Думаю, я должен добавить, что вероятная причина, по которой они избегали поплавков, - это то, что float не может точно представлять все числа, что важно, если вы имеете дело с деньгами.
Ответ 2
Есть очень веские причины, чтобы избежать поплавков. С помощью float вы не можете надежно выполнять сравнения, такие как ==, > , < и т.п. по причине с плавающей запятой шум. При любой операции с плавающей точкой вы накапливаете шум. Он начинается с очень маленьких цифр, появляющихся в самом конце, например, 1.000... 002, но в конечном итоге он может накапливаться, например, 1.0000000453436.
Использование str() может работать для вас, если вы не выполняете много вычислений с плавающей запятой, но если вы выполняете множество вычислений, то с плавающей точкой в конечном итоге будет достаточно большой, чтобы str() дала вам неправильные ответ.
В сумме, если
(1) вы не выполняете много вычислений с плавающей запятой или
(2) вам не нужно делать сравнения, такие как ==, > , < и т.д
тогда вы можете быть в порядке.
Если вы хотите быть уверенным, удалите все коды с плавающей точкой.