classification
Title: Missing fixer for changed round() behavior
Type: behavior Stage:
Components: 2to3 (2.x to 3.x conversion tool) Versions:
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: benjamin.peterson, mark.dickinson, priska, terry.reedy
Priority: normal Keywords:

Created on 2015-06-07 10:58 by priska, last changed 2018-06-11 14:18 by taleinat.

Messages (6)
msg244951 - (view) Author: priska (priska) Date: 2015-06-07 10:58
The behavior of the round() function has changed between Python 2.x and Python3.x. 

From the release notes of Python 3.0: "The round() function rounding strategy and return type have changed. Exact halfway cases are now rounded to the nearest even result instead of away from zero. (For example, round(2.5) now returns 2 rather than 3.) round(x[, n]) now delegates to x.__round__([n]) instead of always returning a float. It generally returns an integer when called with a single argument and a value of the same type as x when called with two arguments."

2to3 conversion does not take this into account and thereby changes code behavior.

Suggested translations:

round(n) -> float(math.floor(n + math.copysign(0.5, n)))

or 

round(n) -> float(math.trunc(n + math.copysign(0.5, n)))
msg244952 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2015-06-07 11:51
This is a bit tricky.  The first expression you suggest doesn't work at all for negative numbers (e.g., producing `-3.0` for `round(-1.8)`).

The second expression *mostly* works as you want, but not entirely.  Some examples:

>>> def f(n): return float(math.trunc(n + math.copysign(0.5, n)))
... 
>>> x = 0.5 - 2**-54
>>> x
0.49999999999999994
>>> f(x)  # should be 0.0
1.0
>>> x = 5e15 + 3.0
>>> x
5000000000000003.0
>>> f(x)  # should be x again
5000000000000004.0

And neither of these addresses the two-argument case of `round`.

It may be better to simply flag this as something that 2to3 can't handle, and that needs a manual check.
msg244953 - (view) Author: priska (priska) Date: 2015-06-07 12:39
Yes, those quick suggestions weren't thoroughly tested at all and turned out to be too short-sighted – thanks for checking, but are you saying the old behavior cannot be emulated or that it is not desirable to to so?
msg244970 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2015-06-07 19:58
The old behaviour certainly can be emulated, but doing that correctly would require 4 or 5 lines of code.  I don't know enough about 2to3 to know whether it can handle that sort of translation, but even if it can, I'd guess that in a good proportion of cases the loss in readability (and possibly also in performance) wouldn't be worth it, and in many cases the difference in behaviour may not matter either.  Given all that, I think it's probably better left as a judgement call on the part of the person doing the 2-to-3 porting.

Here's a translation that's immune from the two issues I pointed out.

    def round_ties_away_from_zero(x):
        n = round(x)
        if abs(x - n) == 0.5:   # x - n is exact; no rounding error
            return x + copysign(0.5, x)
        else:
            return float(n)
msg245268 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2015-06-12 19:22
The fix would be to add a 'round' function at the top of a module, after the imports.  This is more the provenance of 2 & 3 compatibility modules, such as 6, which might have both round2 and round3 functions.
msg245296 - (view) Author: Tal Einat (taleinat) * (Python committer) Date: 2015-06-13 06:45
See the existing issue and discussion about this on the six library's issue tracker (opened nearly a year ago):

https://bitbucket.org/gutworth/six/issue/94/introduce-sixround
History
Date User Action Args
2018-06-11 14:18:30taleinatsetnosy: - taleinat
2015-06-13 06:45:22taleinatsetnosy: + taleinat
messages: + msg245296
2015-06-12 19:22:20terry.reedysetnosy: + terry.reedy
messages: + msg245268
2015-06-08 00:12:37ned.deilysetnosy: + benjamin.peterson
2015-06-07 19:58:34mark.dickinsonsetmessages: + msg244970
2015-06-07 12:39:49priskasetmessages: + msg244953
2015-06-07 11:51:34mark.dickinsonsetnosy: + mark.dickinson
messages: + msg244952
2015-06-07 10:58:41priskacreate