2016-11-16 11 views
9

Может ли кто-нибудь объяснить или предложить исправить причину, почему, когда я округляю десятичное число в Python 3 с контекстом, округленным до половины, он округляется от 2,5 до 2, тогда как в Python 2 он округляется правильно 3:Python 3 Десятичное округление вниз с использованием контекста ROUND_HALF_UP

Python 3.4.3 и 3.5.2:

>>> import decimal 
>>> context = decimal.getcontext() 
>>> context.rounding = decimal.ROUND_HALF_UP 
>>> round(decimal.Decimal('2.5')) 
2 
>>> decimal.Decimal('2.5').__round__() 
2 
>>> decimal.Decimal('2.5').quantize(decimal.Decimal('1'), rounding=decimal.ROUND_HALF_UP) 
Decimal('3') 

Python 2.7.6:

>>> import decimal 
>>> context = decimal.getcontext() 
>>> context.rounding = decimal.ROUND_HALF_UP 
>>> round(decimal.Decimal('2.5')) 
3.0 
>>> decimal.Decimal('2.5').quantize(decimal.Decimal('1'), rounding=decimal.ROUND_HALF_UP) 
Decimal('3') 

ответ

9

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

Если вы используете опциональный параметр ndigits, когда вы вызываете round(), вы вернетесь к десятичному результату, и в этом случае он будет действовать так, как вы ожидали.

Python 3.4.1 (default, Sep 24 2015, 20:41:10) 
[GCC 4.9.2 20150212 (Red Hat 4.9.2-6)] on linux 
Type "help", "copyright", "credits" or "license" for more information. 
>>> import decimal 
>>> context = decimal.getcontext() 
>>> context.rounding = decimal.ROUND_HALF_UP 
>>> round(decimal.Decimal('2.5'), 0) 
Decimal('3') 

я не нашел, где это документально подтверждено, что round(someDecimal) возвращает Int, но round(someDecimal, ndigits) возвращает десятичное, но это, кажется, что происходит в Python 3.3 и более поздних. В Python 2.7 вы всегда возвращаете float при вызове round(), но Python 3.3 улучшил интеграцию Decimal с встроенными Python.

Как отмечено в комментарии, round() делегатов Decimal.__round__() и что на самом деле показывает такое же поведение:

>>> Decimal('2.5').__round__() 
2 
>>> Decimal('2.5').__round__(0) 
Decimal('3') 

Хочу отметить, что документация Fraction говорит:

__round__() 
__round__(ndigits) 
The first version returns the nearest int to self, rounding half to even. 
The second version rounds self to the nearest multiple of Fraction(1, 10**ndigits) 
(logically, if ndigits is negative), again rounding half toward even. 
This method can also be accessed through the round() function. 

Таким образом, поведение согласуется в том, что без аргумента он изменяет тип результата и округляет половину до четного, однако кажется, что Decimal не документирует поведение своего метода __round__ ,

Редактировать, чтобы отметить, как говорит Барри Херли в комментариях, round() документируется как возвращающий int, если вызывается без необязательных аргументов и «значение с плавающей запятой», если предоставляется дополнительный аргумент. https://docs.python.org/3/library/functions.html#round

+0

Спасибо @ Duncan за объяснение, всегда полезно попытаться понять эти вещи. Кажется очень странным, что возвращается другой _type_ в зависимости от того, укажите ли вы ndigits или нет. –

+1

В качестве дополнения, python 3 docs заявляют, что он делегирует 'number .__ round __ (ndigits)', а утилиты 'decimal' реализует свой собственный метод' __round__' –

+1

@Dunca, он выглядит как int, а не float: «Если ndigits опущен или None, он возвращает ближайшее целое к его вводу». https://docs.python.org/3/library/functions.html#round –

2

Расширение на ответ @ Duncan, встроенная функция round изменилась между python 2 и python 3 до округления до ближайшего четного числа (что является нормой в статистике).

python2 документы:

... если два кратных одинаково близки, округление покончили от 0 (так, например, круглые (0,5) составляет 1,0 и круглые (-0,5) является - 1.0).

Python3 документы:

... если два кратных одинаково близки, округление производится в сторону даже выбора (так, например, как круглые (0,5) и круглые (-0.5) 0, и раунд (1.5) 2)

С round превращается в float, если аргумент не задан для ndigits (кредита @ ответа Дункан), round ведет себя так же, как это было бы для float с.

Примеры (в Python3):

>>> round(2.5) 
2 
>>> round(2.500000001) 
3 
>>> round(3.5) 
4 
+0

Спасибо @Billy, но оба раунда (1.5) и раунд (2.5) дают 2 в Python 3, что, я полагаю, связано с тем, как эти 1.5 и 2.5 поплавки представлены внутри. round в Python 2 возвращает float для них, в сравнении с ints int Python 3. –

+2

Это не имеет ничего общего с внутренними представлениями. Реализация с плавающей запятой Python может представлять собой 1,5 и 2,5 * точно * (т. Е. Ошибок округления с плавающей запятой, как вы бы получили с номером 1.1). Так как 1.5 равно * ровно * равным на расстоянии от 1 и 2, питон 3 раунда до ближайшего четного числа, равный 2. Точно так же 2.5 * точно * между 2 и 3, поэтому округляется до ближайшего четного числа, что снова 2. – Billy

1

Это сочетание изменений между режимом округления round в Python 2 против 3 и повторной реализации Decimal от Python к C (См «Другое окончательные крупномасштабные изменения " в Features for 3.3 section PEP 398).

Для round стратегия округления изменилась, как видно из What's New In Python 3.0 [Также см. Python 3.x rounding behavior]. Кроме того, round в Python 3 сначала пытается find an appropriate __round__ метод, определенный для объекта передается:

>>> round('1') 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: type str doesn't define __round__ method 

В то время как в Python 2.x он сначала пытается coerce it specifically to a float, а затем вокруг него:

>>> round('1') 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: a float is required 

Для Decimal, в Python 2, реализация даже не имела метода __round__, который должен называться:

>>> Decimal.__round__ 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
AttributeError: type object 'Decimal' has no attribute '__round__' 

Итак, позвонив по телефону round по объекту Decimal, он принудил его к float, который получил rounded using _Py_double_round; это привело к тому, что float всегда возвращался независимо от того, было ли задано значение для ndigits. decimal реализован в чистом Python для 2.x и (был?) Для Python 3 до 3.2.

In Python 3.3 it got shinny new __round__ method, как он был вновь реализован в C:

>>> Decimal.__round__ 
<method '__round__' of 'decimal.Decimal' objects> 

и теперь он получает подобран round когда round(<Decimal_object>) вызывается.

Это отображается в PyDec_Round в C, теперь returns a PyLong (целое число), используя контекст по умолчанию (ROUND_HALF_EVEN), если аргумент ndigits не подается, и, если он есть, вызывает quantize на него, и возвращает новый закругленный Decimal объект.