Проблемы с округлением десятичных дробей (python) - PullRequest
22 голосов
/ 15 января 2012

В моей программе очень важна десятичная точность.
Многие из моих расчетов должны быть точными для многих десятичных знаков (например, 50).

Поскольку я использую Python, я использовал десятичный модуль (с context (). prec = 99. т.е.; Устанавливать 99 десятичных знаков точности при создании экземпляра десятичного объекта )
так как питонические поплавки не позволяют достичь такой точности.

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

round(decimal.Decimal('2.000000000000000000001'),50)

# Number is 1e-21.   There are 21 decimal places, much less than 50.

И все же результат равен 2,0 вместо 2.000000000000000000001
Функция округления не округляет до 50. Гораздо меньше!

Да, я убедился, что округление не происходит при создании экземпляра объекта Decimal, а после вызова раунда.
Я всегда передаю строки, представляющие числа с плавающей точкой, конструктору Decimal, а не числам с питоном.

Почему функция округления делает это со мной?
(Я понимаю, что он, вероятно, изначально был разработан для pythonic float, у которого никогда не может быть столько десятичных знаков, но в документации утверждается, что объект Decimal прекрасно интегрируется в код python и совместим со встроенными функциями python!)

Спасибо, обильно!
(Это меня очень расстраивает, так как эта проблема подрывает использование всей программы)

Технические характеристики:
питон 2.7.1
Windows 7
десятичный модуль (встроенный)

Ответы [ 5 ]

39 голосов
/ 15 января 2012

Поскольку round () принудительно вводит данные в обычный двоичный тип с плавающей запятой, предпочтительным способом округления десятичных объектов является метод quantize () метод:

>>> from decimal import Decimal
>>> d = Decimal('2.000000000000000000001')

>>> d.quantize(Decimal(10) ** -20)     # Round to twenty decimal places
Decimal('2.00000000000000000000')

Чтобы отсеять конечные нули, примените normalize () к округленному результату:

>>> d = Decimal('2.000000000000000000001')
>>> d.quantize(Decimal(10) ** -20).normalize()
Decimal('2')
7 голосов
/ 15 января 2012

Проблема, насколько я могу судить, в том, что round() возвращает тип Python float, а не тип Decimal.Таким образом, не имеет значения, какую точность вы устанавливаете в модуле decimal, потому что после вызова round() у вас больше не будет Decimal.

Чтобы обойти это, вам, вероятно, придетсяпридумайте альтернативный способ округления ваших чисел, который не зависит от round().Например, предложение Раймонда.

Этот короткий пример идеона может показаться иллюстративным: http://ideone.com/xgPL9

4 голосов
/ 27 февраля 2013

Попробуйте следующее ...

from decimal import Decimal, ROUND_HALF_UP, getcontext

getcontext().prec = 51

def round_as_decimal(num, decimal_places=2):
    """Round a number to a given precision and return as a Decimal

    Arguments:
    :param num: number
    :type num: int, float, decimal, or str
    :returns: Rounded Decimal
    :rtype: decimal.Decimal
    """
    precision = '1.{places}'.format(places='0' * decimal_places)
    return Decimal(str(num)).quantize(Decimal(precision), rounding=ROUND_HALF_UP)

round_as_decimal('2.000000000000000000001', decimal_places=50)

Надеюсь, это поможет!

4 голосов
/ 15 января 2012

Использование round() для любой цель имеет трудности:

(1) В Python 2.7 round (десятичный ('39 .142 ')) производит 39,14, то есть число с плавающей запятой;3.2 создает десятичное число.

>>> import decimal; round(decimal.Decimal('2.000000000000000000001'),50)
2.0   <<<=== ***float***

(2) round () имеет только один режим округления;Decimal.quantize имеет много.

(3) Для всех типов ввода изменился режим одного округления: Python 2,7 округляется от 0 (ожидается в большинстве бизнес-приложений), тогда как Python 3,2 округляется до ближайшего, даже кратного 10** - n (меньше ожидаемого в бизнесе).

Использование Decimal.quantize()

2 голосов
/ 28 марта 2013

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

    def dround(decimal_number, decimal_places):
        return decimal_number.quantize(Decimal(10) ** -decimal_places)
...