Зло в питоне десятичное / плавающее - PullRequest
16 голосов
/ 15 ноября 2010

У меня большой объем кода на Python, который пытается обрабатывать числа с точностью до 4 десятичных знаков, и я застрял на Python 2.4 по многим причинам. Код выполняет очень упрощенную математику (это код управления кредитами, который в основном берет или добавляет кредиты)

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

Я попал в такие случаи:

>>> 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

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

Я специально использовал "str" ​​вместо "repr", поскольку это исправляет некоторые случаи округления чисел с плавающей точкой. Э.Г.

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

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

Ответы [ 2 ]

3 голосов
/ 15 ноября 2010

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

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

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

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

3 голосов
/ 15 ноября 2010

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

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

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

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

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

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

...