classification
Title: Inconsistent round behavior between float and int
Type: behavior Stage:
Components: Versions: Python 3.6, Python 3.5
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: rhettinger Nosy List: Jonatan Skogsfors, mark.dickinson, python-dev, rhettinger
Priority: normal Keywords: patch

Created on 2016-09-02 06:52 by Jonatan Skogsfors, last changed 2016-09-04 18:29 by python-dev. This issue is now closed.

Files
File name Uploaded Description Edit
fix_round_default_none_with_test.diff rhettinger, 2016-09-02 08:25 review
Messages (8)
msg274205 - (view) Author: Jonatan Skogsfors (Jonatan Skogsfors) Date: 2016-09-02 06:52
Theo error handling for round is different for float and int. 

Python 3.5.1 (default, Apr 18 2016, 11:46:32) 
[GCC 4.2.1 Compatible Apple LLVM 7.3.0 (clang-703.0.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> round(1.0, None)
1
>>> round(1, None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object cannot be interpreted as an integer
msg274208 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2016-09-02 08:13
The different data types make different choices:

>>> from decimal import Decimal
>>> from fractions import Fraction
>>> (1).__round__(None)
Traceback (most recent call last):
  File "<pyshell#39>", line 1, in <module>
    (1).__round__(None)
TypeError: 'NoneType' object cannot be interpreted as an integer
>>> (1.0).__round__(None)
1
>>> Decimal(1).__round__(None)
Traceback (most recent call last):
  File "<pyshell#41>", line 1, in <module>
    Decimal(1).__round__(None)
TypeError: optional arg must be an integer
>>> Fraction(1, 1).__round__(None)
1
>>> from _pydecimal import Decimal
>>> Decimal(1).__round__(None)
1

For Fraction and _pydecimal, the behavior comes from using None as a placeholder (which is common and normal in pure python code).  For float there is explicit code to test for the None case.   For int, the None test was omitted (perhaps a mistake) and the error is raised by PyNumber_Index.

Looking through tests, only Lib/test/test_float.py tests for None being allowable.  Elsewhere, it seems to be an implementation detail.

The old Python 2 version of the round() function never let None be passed in (because it used PyArg_ParseTupleAndKeywords(args, kwds, "d|i:round").  That logic seems to get lost in the Python 3 version when __round__ was introduced.

To resolve the differences, I think the round() function should explicitly check for None and replace it with zero before calling the underling __round__ functions where we would allow variable according the needs of the implementation.
msg274212 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2016-09-02 08:50
> I think the round() function should explicitly check for None and replace it with zero

That would be a change in behaviour: `round(x)` is not the same as `round(x, 0)`. For most types, `round(x)` returns an `int`, while `round(x, 0)` returns something of the same type as `x`. Instead, I think we should add the check for `None` to `int`s `__round__` implementation.

>>> round(1.3, 0)
1.0
>>> round(1.3)
1
>>> round(1.3, None)
1
msg274213 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2016-09-02 08:52
Ah, sorry; now that I've looked at the patch, I see I misunderstood. You're not replacing None with zero; you're replacing it with NULL.

Patch LGTM.
msg274214 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2016-09-02 08:55
The test could be strengthened to add a check that the types of `round(x)` and `round(x, None)` match.
msg274265 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2016-09-02 17:44
For the record, the float behaviour was changed (for Python 3.5) in http://bugs.python.org/issue19933. There didn't seem to be any particularly strong motivation for that change, but it's done now.
msg274289 - (view) Author: Roundup Robot (python-dev) Date: 2016-09-03 08:55
New changeset c3c4d8e4ca1a by Raymond Hettinger in branch '3.5':
Issue 27936: Fix inconsistent round() behavior between float and int
https://hg.python.org/cpython/rev/c3c4d8e4ca1a
msg274376 - (view) Author: Roundup Robot (python-dev) Date: 2016-09-04 18:29
New changeset 7108f2a708c9 by Raymond Hettinger in branch '3.5':
Issue 27936: Update doc for round() to indicate that None is an allowable argument.
https://hg.python.org/cpython/rev/7108f2a708c9
History
Date User Action Args
2016-09-04 18:29:23python-devsetmessages: + msg274376
2016-09-03 08:57:57rhettingersetstatus: open -> closed
assignee: rhettinger
resolution: fixed
versions: + Python 3.6
2016-09-03 08:55:50python-devsetnosy: + python-dev
messages: + msg274289
2016-09-02 17:44:35mark.dickinsonsetmessages: + msg274265
2016-09-02 08:55:34mark.dickinsonsetmessages: + msg274214
2016-09-02 08:52:49mark.dickinsonsetmessages: + msg274213
2016-09-02 08:50:47mark.dickinsonsetnosy: + mark.dickinson
messages: + msg274212
2016-09-02 08:25:14rhettingersetfiles: - fix_round_default_none.diff
2016-09-02 08:25:05rhettingersetfiles: + fix_round_default_none_with_test.diff
2016-09-02 08:17:25rhettingersetfiles: + fix_round_default_none.diff
keywords: + patch
2016-09-02 08:13:46rhettingersetnosy: + rhettinger
messages: + msg274208
2016-09-02 06:52:55Jonatan Skogsforscreate