Barry Hurley - 1 year ago 69

Python Question

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

Answer Source

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