[英]Python 3 Decimal rounding half down with ROUND_HALF_UP context
Can anybody explain or propose a fix for why when I round a decimal in Python 3 with the context set to round half up, it rounds 2.5 to 2, whereas in Python 2 it rounds correctly to 3:任何人都可以解释或提出修复为什么当我在 Python 3 中四舍五入小数并将上下文设置为四舍五入时,它将 2.5 舍入为 2,而在 Python 2 中它正确地四舍五入为 3:
Python 3.4.3 and 3.5.2: 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: 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')
Notice that when you call round
you are getting a float value as a result, not a Decimal
. 请注意,当您调用
round
您将获得一个浮点值,而不是Decimal
。 round
is coercing the value to a float and then rounding that according to the rules for rounding a float. round
是将值强制转换为float,然后根据舍入浮点数的规则舍入该值。
If you use the optional ndigits
parameter when you call round()
you will get back a Decimal result and in this case it will round the way you expected. 如果在调用
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')
I haven't found where it is documented that round(someDecimal)
returns an int but round(someDecimal, ndigits)
returns a decimal, but that seems to be what happens in Python 3.3 and later. 我还没有找到
round(someDecimal)
文件round(someDecimal)
返回一个int而round(someDecimal, ndigits)
返回一个小数,但这似乎是Python 3.3及更高版本中发生的情况。 In Python 2.7 you always get a float back when you call round()
but Python 3.3 improved the integration of Decimal
with the Python builtins. 在Python 2.7中,当你调用
round()
时,你总是得到一个浮点数,但Python 3.3改进了Decimal
与Python内置函数的集成。
As noted in a comment, round()
delegates to Decimal.__round__()
and that indeed shows the same behaviour: 正如评论中所指出的,
round()
委托给Decimal.__round__()
,这确实表现出相同的行为:
>>> Decimal('2.5').__round__()
2
>>> Decimal('2.5').__round__(0)
Decimal('3')
I note that the documentation for Fraction
says: 我注意到
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.
Thus the behaviour is consistent in that with no argument it changes the type of the result and rounds half to even, however it seems that Decimal
fails to document the behaviour of its __round__
method. 因此行为是一致的,因为没有参数它会改变结果的类型并将一半
__round__
到偶数,但是似乎Decimal
无法记录其__round__
方法的行为。
Edit to note as Barry Hurley says in the comments, round()
is documented as returning a int
if called without the optional arguments and a "floating point value" if given the optional argument. 编辑注意Barry Hurley在评论中说,
round()
被记录为如果在没有可选参数的情况下调用则返回int
,如果给出可选参数则返回“浮点值”。 https://docs.python.org/3/library/functions.html#round https://docs.python.org/3/library/functions.html#round
Expanding on @Duncan's answer, the round
builtin function changed between python 2 and python 3 to round to the nearest even number (which is the norm in statistics). 扩展@Duncan的答案,
round
内置函数在python 2和python 3之间改变,以舍入到最接近的偶数(这是统计中的标准)。
Python2 docs: 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).
...如果两个倍数相等,则舍入远离0(因此,例如,round(0.5)为1.0,round(-0.5)为-1.0)。
Python3 docs: 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)
...如果两个倍数相等,则向均匀选择进行舍入(例如,圆形(0.5)和圆形(-0.5)均为0,圆形(1.5)为2)
Since round
converts to float
if no argument is given for ndigits
(credit to @Duncan's answer), round
behaves the same way as it would for float
s. 如果没有为
ndigits
给出参数, round
转换为float
(归功于ndigits
的答案), round
行为方式与float
s相同。
Examples (in python3): 示例(在python3中):
>>> round(2.5)
2
>>> round(2.500000001)
3
>>> round(3.5)
4
This is a combination of changes between the rounding mode of round
in Python 2 vs 3 and the re-implementation of Decimal
from Python to C
(See "Other final large-scale changes" in the Features for 3.3 section PEP 398 ). 这是舍入模式之间的变化的组合
round
在Python 2比3和重新实施的Decimal
从Python来C
(参见在“其它最终大规模变化” 功能为3.3部分PEP 398 )。
For round
, the rounding strategy changed as can be seen in What's New In Python 3.0 [Also see Python 3.x rounding behavior ]. 对于
round
,舍入策略已经改变,可以在Python 3.0中的新功能中看到[另请参阅Python 3.x舍入行为 ]。 Additionally, round
in Python 3
first tries to find an appropriate __round__
method defined for the object passed: 另外,Python
3
round
首先尝试找到为传递的对象定义的适当的__round__
方法:
>>> round('1')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: type str doesn't define __round__ method
While in Python 2.x
it first tries to coerce it specifically to a float
and then round it: 在Python
2.x
它首先尝试将它专门强制转换为float
,然后将其舍入:
>>> round('1')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: a float is required
For Decimal
, in Python 2
, the implementation even lacked a __round__
method to be called: 对于
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__'
So, calling round
on a Decimal
object coerced it to a float
which got rounded using _Py_double_round
; 因此,呼吁
round
一个关于Decimal
对象裹挟到一个float
这得到了使用四舍五入_Py_double_round
; this resulted in a float
always getting returned irregardless of if a value for ndigits
was supplied. 这导致
float
总是返回, ndigits
是否提供了ndigits
的值。 decimal
is implemented in pure Python for 2.x
and (was?) for Python 3
until 3.2
. decimal
是用纯Python实现2.x
和(是?)适用于Python 3
直到3.2
。
In Python 3.3
it got shinny new __round__
method as it was re-implemented in C
: 在Python
3.3
,当它在C
重新实现时, 它有了新的__round__
方法 。
>>> Decimal.__round__
<method '__round__' of 'decimal.Decimal' objects>
and now, it gets picked up by round
when round(<Decimal_object>)
is invoked. 而现在,它会在
round(<Decimal_object>)
被调用时被round
round(<Decimal_object>)
拾取。
This, mapped to PyDec_Round
in C
, now 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
on it and returns a new rounded Decimal
object. 此,映射到
PyDec_Round
在C
,现在返回PyLong使用默认上下文((一个整数) ROUND_HALF_EVEN
)如果参数ndigits
不供给,如果它是,来电quantize
它,并返回一个新的圆形Decimal
对象。
German and Swiss laws require you to round up VAT from 0.005 to 0.01 ("kaufmännisches Runden") .德国和瑞士法律要求您将增值税从 0.005 舍入到 0.01 (“kaufmännisches Runden”) 。 I played around with
Decimal
and this what I ended up with:我玩了
Decimal
,结果是这样的:
from decimal import *
class Number(object):
'''generic class for rounding:
Banking: ROUND_HALF_EVEN # default
Germany: ROUND_HALF_UP # for VAT
Switzerland: ROUND_DOWN # for some taxes
ROUND_HALF_UP # for some taxes like VAT
'''
def __init__(self, digits=2, rounding=ROUND_HALF_UP):
getcontext().rounding=rounding
self.base = Decimal(10) ** -digits
def round(self, value, digits=None):
if digits:
base = Decimal(10) ** - digits
else:
base = self.base
return float(Decimal(str(value)).quantize(base))
Example:例子:
>>> a = Number()
>>> a.round(1301.685)
1301.69
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.