Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.4.3 and 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')
like image 979
Barry Hurley Avatar asked Nov 16 '16 11:11

Barry Hurley


People also ask

How do you round down .5 in Python?

Method 1: Using Built-in round() Function. In Python there is a built-in round() function which rounds off a number to the given number of digits. The function round() accepts two numeric arguments, n and n digits and then returns the number n after rounding it to n digits.

How do you round to a specific decimal in Python?

Round() Round() is a built-in function available with python. It will return you a float number that will be rounded to the decimal places which are given as input. If the decimal places to be rounded are not specified, it is considered as 0, and it will round to the nearest integer.

How do you round to 2 decimal places in Python?

Python's round() function requires two arguments. First is the number to be rounded. Second argument decides the number of decimal places to which it is rounded. To round the number to 2 decimals, give second argument as 2.


2 Answers

Notice that when you call round you are getting a float value as a result, not a Decimal. round is coercing the value to a float and then rounding that according to the rules for rounding a 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.

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

As noted in a comment, round() delegates to Decimal.__round__() and that indeed shows the same behaviour:

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

I note that the documentation for Fraction says:

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

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. https://docs.python.org/3/library/functions.html#round

like image 150
Duncan Avatar answered Oct 20 '22 13:10

Duncan


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

Python2 docs:

...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 docs:

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

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

Examples (in python3):

>>> round(2.5)
2
>>> round(2.500000001)
3
>>> round(3.5)
4
like image 21
Billy Avatar answered Oct 20 '22 13:10

Billy