Changing the behavior of (builtins.)round (via the __round__ dunder) to return an integer

https://github.com/numpy/numpy/issues/3511 proposed (nearly three years ago) to return an integer when `builtins.round` (which calls the `__round__ dunder method, and thereafter called `round` (... not to be confused with `np.round`)) is called with a single argument. Currently, `round` returns a floating scalar for numpy scalars, matching the Python2 behavior. Python3 changed the behavior of `round` to return an int when it is called with a single argument (otherwise, the return type matches the type of the first argument). I believe this is more intuitive, and is arguably becoming more important now that numpy is deprecating (via a VisibleDeprecationWarning) indexing with a float: having to write array[int(round(some_float))] is rather awkward. (Note that I am suggesting to switch to the new behavior regardless of the version of Python.) Note that currently the `__round__` dunder is not implemented for arrays (... see https://github.com/numpy/numpy/issues/6248) so it would be feasible to always return a signed integer of the same size with an OverflowError on overflow (at least, any floating point that is round-able without loss of precision will be covered). If `__round__` ends up being implemented for ndarrays too, I guess the correct behavior will be whatever we come up for signaling failure in integer operations (see current behavior of `np.array([0, 1]) // np.array([0, 1])`). Also note the comment posted by @njsmith on the github issue thread: I'd be fine with matching python here, but we need to run it by the mailing list. Not clear what the right kind of deprecation is... Normally FutureWarning since there's no error involved, but that would both be very annoying (basically makes round unusable -- you get this noisy warning even if what you're doing is round(a).astype(int)), and the change is relatively low risk compared to most FutureWarning changes, since the actual values returned are identical before and after the change. Thoughts? Antony

On Wed, Apr 13, 2016 at 12:42 AM, Antony Lee <antony.lee@berkeley.edu> wrote:
(Note that I am suggesting to switch to the new behavior regardless of the version of Python.)
I would lean towards making this change only for Python 3. This is arguably more consistent with Python than changing the behavior on Python 2.7, too. The most obvious way in which a float being surprisingly switched to an integer could cause silent bugs (rather than noisy TypeErrors) is if the number is used in division. True division in Python 3 eliminates this risk. Generally, I agree with your reasoning. It would be unfortunate to be stuck with this legacy behavior forever.

On Wed, Apr 13, 2016 at 8:06 AM, <josef.pktd@gmail.com> wrote:
The difference is that Python 3 has looooong ints, (and doesn't have to overflow, AFAICS)
This is a good point. But if your float is so big that rounding it to an integer would overflow int64, rounding is already a no-op. I'm sure this has been done before but I would guess it's quite rare. I would be OK raising in this situation, especially because np.around will still be around returning floats.
what happens with nan? I guess inf would overflow?
builtins.round raises for both of these (in Python 3) and I would propose copying this behavior: In [52]: round(float('inf')) --------------------------------------------------------------------------- OverflowError Traceback (most recent call last) <ipython-input-52-798e0e9243d6> in <module>() ----> 1 round(float('inf')) OverflowError: cannot convert float infinity to integer In [53]: round(float('nan')) --------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-53-e989485df64c> in <module>() ----> 1 round(float('nan')) ValueError: cannot convert float NaN to integer

On Wed, Apr 13, 2016 at 12:42 AM, Antony Lee <antony.lee@berkeley.edu> wrote:
(Note that I am suggesting to switch to the new behavior regardless of the version of Python.)
I would lean towards making this change only for Python 3. This is arguably more consistent with Python than changing the behavior on Python 2.7, too. The most obvious way in which a float being surprisingly switched to an integer could cause silent bugs (rather than noisy TypeErrors) is if the number is used in division. True division in Python 3 eliminates this risk. Generally, I agree with your reasoning. It would be unfortunate to be stuck with this legacy behavior forever.

On Wed, Apr 13, 2016 at 8:06 AM, <josef.pktd@gmail.com> wrote:
The difference is that Python 3 has looooong ints, (and doesn't have to overflow, AFAICS)
This is a good point. But if your float is so big that rounding it to an integer would overflow int64, rounding is already a no-op. I'm sure this has been done before but I would guess it's quite rare. I would be OK raising in this situation, especially because np.around will still be around returning floats.
what happens with nan? I guess inf would overflow?
builtins.round raises for both of these (in Python 3) and I would propose copying this behavior: In [52]: round(float('inf')) --------------------------------------------------------------------------- OverflowError Traceback (most recent call last) <ipython-input-52-798e0e9243d6> in <module>() ----> 1 round(float('inf')) OverflowError: cannot convert float infinity to integer In [53]: round(float('nan')) --------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-53-e989485df64c> in <module>() ----> 1 round(float('nan')) ValueError: cannot convert float NaN to integer
participants (3)
-
Antony Lee
-
josef.pktd@gmail.com
-
Stephan Hoyer