Подтвердить что ты не робот

Зло в 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")

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

4b9b3361

Ответ 1

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

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

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

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

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

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

Ответ 2

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

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

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

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