Python 3 rounding behavior in Python 2

python

Solution

Python 3 round in Python 2

The function can look like this:

def py3round(f):
    if abs(round(f)-f) == 0.5:
        return 2.0*round(f/2.0);
    return round(f)

# Python 3            apply round to ... -.1 -.75 -.5 -.25 0 .25 .5 .75 ...
>>> ' '.join(map(str, map(int, [round(i * 0.25) for i in range(-20, 20)])))
'-5 -5 -4 -4 -4 -4 -4 -3 -3 -3 -2 -2 -2 -2 -2 -1 -1 -1 0 0 0 0 0 1 1 1 2 2 2 2 2 3 3 3 4 4 4 4 4 5'
# Python 2            apply round to ... -.1 -.75 -.5 -.25 0 .25 .5 .75 ...
>>> ' '.join(map(str, map(int, [py3round(i * 0.25) for i in range(-20, 20)])))
'-5 -5 -4 -4 -4 -4 -4 -3 -3 -3 -2 -2 -2 -2 -2 -1 -1 -1 0 0 0 0 0 1 1 1 2 2 2 2 2 3 3 3 4 4 4 4 4 5'

Let me clarify what round does in `bltinmodule.c`

if hasattr(args[0], '__round__'):
    return args[0].__round__(*args[1:])
else: 
    raise TypeError("type %.100s doesn't define __round__ method")

So round actually does almost nothing. It depends on the objects passed to it. That leads to `floatobject.c` function `static PyObject *double_round(double x, int ndigits)`

z = round(y);
if (fabs(y-z) == 0.5)
    /* halfway between two integers; use round-half-even */
    z = 2.0*round(y/2.0);

I used the knowledge of these lines in my function above.

Python 2 round in Python 3

I think you need to write a new function.

def python2round(f):
    if round(f + 1) - round(f) != 1:
        return f + abs(f) / f * 0.5
    return round(f)

The if statement handles the case that `i + 0.5` and `i + 1.5` are rounded into different directions = to even numbers and halves. In this case the rounding is done away from zero.

# in Python 2          apply round to ... -.1 -.75 -.5 -.25 0 .25 .5 .75 ...
>>> ' '.join(map(str, map(int, [round(i * 0.25) for i in range(-20, 20)])))
'-5 -5 -5 -4 -4 -4 -4 -3 -3 -3 -3 -2 -2 -2 -2 -1 -1 -1 -1 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3 4 4 4 4 5 5'
# in Python 3          apply round to ... -.1 -.75 -.5 -.25 0 .25 .5 .75 ...
>>> ' '.join(map(str, map(int, [python2round(i * 0.25) for i in range(-20, 20)])))
'-5 -5 -5 -4 -4 -4 -4 -3 -3 -3 -3 -2 -2 -2 -2 -1 -1 -1 -1 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3 4 4 4 4 5 5'

Do you need a solution with the second argument to round, `ndigits`?

Problem

In Python 2.x, the built-in `round` has the following behavior: 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) In Python 3.x, this has changed to the more common: 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). Is there an easy way to get this behavior in Python 2.x? Unfortunately, the `future_builtins` module doesn't include this. Maybe there's another similar module I haven't found yet? Or, another way to pull Python 3.x functions into Python 2.x? Obviously, I could write a new function that produces the desired behavior, but I'm more curious if a solution exists that uses the actual Python 3.x function, to avoid adding unnecessary complexity and code to maintain.

Original source