Python 3 小数四舍五入 ROUND_HALF_UP 上下文

Python 3 Decimal rounding half down with ROUND_HALF_UP context

任何人都可以解释或建议解决为什么当我在 Python 3 中舍入小数时将上下文设置为四舍五入一半,它将 2.5 舍入为 2,而在 Python 2 中它正确舍入为 3:

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

请注意,当您调用 round 时,您得到的是一个浮点值,而不是 Decimalround 将值强制转换为浮点数,然后根据四舍五入浮点数的规则对其进行四舍五入。

如果您在调用 round() 时使用可选的 ndigits 参数,您将得到一个 Decimal 结果,在这种情况下,它将按您预期的方式取整。

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) returns 一个整数但是 round(someDecimal, ndigits) returns 一个小数的地方,但这似乎是 Python 3.3 及更高版本。在 Python 2.7 中,当您调用 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__ 方法的行为.

编辑注意,正如 Barry Hurley 在评论中所说,如果在没有可选参数的情况下调用,round() 被记录为返回 int,如果给定可选参数,则返回 "floating point value"。 https://docs.python.org/3/library/functions.html#round

扩展@Duncan 的回答,round 内置函数在 python 2 和 python 3 之间更改为四舍五入到最接近的偶数(这是统计中的标准)。

Python2 文档:

...if two multiples are equally close, rounding is done away from 0 (so, for example, round(0.5) is 1.0 and round(-0.5) is -1.0).

Python3 文档:

...if two multiples are equally close, rounding is done toward the even choice (so, for example, both round(0.5) and round(-0.5) are 0, and round(1.5) is 2)

由于 round 转换为 float 如果没有为 ndigits 给出参数(归功于@Duncan 的回答),round 的行为方式与floats.

示例(在 python3 中):

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

这是 Python 2 vs 3 中 round 的舍入模式和 Python 到 [= 的 Decimal 的重新实现之间的变化的组合16=](参见Features for 3.3 section PEP 398中的"Other final large-scale changes")。

对于 round,舍入策略发生了变化,在为传递的对象定义的 What's New In Python 3.0 [Also see Python 3.x rounding behavior ]. Additionally, round in Python 3 first tries to 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__'

因此,在 Decimal 对象上调用 round 将其强制转换为 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(<Decimal_object>) 时,它会被 round 拾取。

这个,映射到 C 中的 PyDec_Round,现在 returns a PyLong (an integer) using the default context (ROUND_HALF_EVEN) if the argument ndigits is not supplied and, if it is, calls quantize 和 returns 一个新的圆形 Decimal 对象。