29

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.

6
  • 3
    I did not realize that 3.x behavior was a thing. I imagine there's an interminably long mailing list thread about that somewhere... Commented Feb 17, 2014 at 21:07
  • 3
    @AndrewGorcester: almost certainly. The purpose (which may or may not be appropriate in any given situation) is to avoid introducing a bias in any direction. For example imagine a set values with 2 digits after the decimal point (that is to say, they're measured to 1/100 of a unit). If you round away from 0 and the values are all positive with each of the 100 possible fractional parts equally represented then you increase the mean by 1/200. Less than the initial accuracy but systematic and so sometimes quite bad. en.wikipedia.org/wiki/Rounding#Round_half_to_even Commented Feb 17, 2014 at 22:31
  • Yep, see IEEE 754 Rounding Rules. Commented Feb 17, 2014 at 22:55
  • Would numpy dependency be an issue? Commented Feb 18, 2014 at 9:07
  • 1
    @AndrewGorcester: I think I found the beginning of that mailing list thread: mail.python.org/pipermail/python-list/2008-April/509669.html Commented Feb 18, 2014 at 18:40

8 Answers 8

6

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?

Sign up to request clarification or add additional context in comments.

4 Comments

Your code seems to implement the Python 2 rounding in Python 3. I think the questioner wanted the reverse, the Python 3 rounding in Python 2.
Blkknght is correct, I was looking for the other way around. More importantly, I'm specifically looking for a solution that brings the exact Python 3 behavior into Python 2 without needing to maintain a new function (although it wouldn't be the end of the world).
Oh sorry.^^ I will have a look into that.. see if something shorter can come out. You will need a new function nevertheless. But once the function meets the specification I do not think you need to maintain it.
Nice that you dug through the python source to implement the exact same function in python. Short of an answer that pulls in the real CPython function, I like this answer best so far.
5

Unless you mind a numpy dependency, numpy.around may do the thing:

>>> from numpy import around
>>> around(0.5)
0
>>> around(-0.5)
-0
>>> around(1.5)
2.0

4 Comments

But we're trying to get the Python 2.x behavior, so that around(-0.5)==-1.0 and around(0.5)==1.0.
@Teepeemm: Nope, other way around (get py3 behavior in py2). This solution works, but isn't ideal.
+1 This is a great solution if you happen to have numpy lying around.
@Teepeemm round(x+10**(-y-1),y) gives 2.x behaviour
3

If you're super-paranoid about floating point rounding issues, you can look into the decimal library where you can configure the rounding mode (defaults to ROUND_HALF_EVEN).

>>> import decimal
>>> from decimal import Decimal
>>> Decimal(0.5).quantize(Decimal('1'))
Decimal('0')
>>> Decimal(1.5).quantize(Decimal('1'))
Decimal('2')

>>> decimal.getcontext().rounding = decimal.ROUND_HALF_UP
>>> Decimal(0.5).quantize(Decimal('1'))
Decimal('1')

Otherwise, I think it's more explicit and more maintainable if you just write your own function or use numpy, rather than wish that you could use a py3 function in py2.

2 Comments

You can define several types of rounding, either by context (as in the answer), or by arg. Ex.: Decimal(-0.5).quantize(Decimal('1', rounding='ROUND_CEILING')). See: docs.python.org/3/library/decimal.html#rounding-modes
Re: rounding modes -- Personally, I prefer ROUND_HALF_UP as I think that's closest to how most people round. Of course, it introduces a rounding bias, and so may not be appropriate for your context.
3

This response has read the original question, and the answer is "Nope, I can't come up with something that uses the Py3 original code."

But for anyone who is wondering what the code would be that would replicate Py3 behavior in Py2 (including the int vs. float behavior), here's an adaptation of the User code above that includes ndigits and the int vs. float distinction for ndigits = None vs 0.

import sys

def py3round(number, ndigits=None):
    '''
    Simulates Python3 rounding behavior in Python 2

    >>> py3round(2.3)
    2
    >>> py3round(2.7)
    3
    >>> py3round(2.7, 0)
    3.0
    >>> py3round(1.5, 0)
    2.0
    >>> py3round(2.5, 0)
    2.0
    >>> py3round(-1.5)
    -2
    >>> py3round(-2.5)
    -2
    '''
    if sys.version_info[0] >= 3:
        if ndigits is not None:
            return round(number, ndigits)
        else:
            return round(number)        

    intIt = True if ndigits is None else False
    ndigits = ndigits if ndigits is not None else 0

    f = number
    if abs(round(f) - f) == 0.5:
        retAmount = 2.0 * round(f / 2.0, ndigits);
    else:
        retAmount = round(f, ndigits)

    if intIt:
        return int(retAmount)
    else:
        return retAmount

Comments

1

Use decimal.Decimal.to_integral_value

For example:

float(decimal.Decimal(1.5).to_integral_value(decimal.ROUND_HALF_EVEN))

The rounding options are documented here. The two options we are concerned with are:

ROUND_HALF_EVEN (to nearest with ties going to nearest even integer),
ROUND_HALF_UP (to nearest with ties going away from zero),

Comments

0
def round_to_even(x):
    if x < 0:
        return -round_to_even(-x)
    ipart, fpart = divmod(x, 1)
    ipart = int(ipart)
    fpartx2 = fpart * 2
    if fpartx2 > 1:
        return ipart + 1
    elif fpartx2 == 1:
        return ipart + (ipart & 1)
    else:
        return ipart

1 Comment

Did you read the question? "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 LOC to maintain."
0

You can use the following pseudocode:

if number is_factor_of_0.5:
      res =  round_down(n,d) --> Returns math.foot() of a number
else:
      res =  round()     # Python's own round
        
return float(res)

I had a similar problem but mine was to use python2 round functionality in python3 , following code worked for me

import math


def round_up(n, decimals = 0):
    multiplier = 10 ** decimals
    return math.ceil(n * multiplier) / multiplier

def python2round(n, d=0):
    if n % 0.5 ==0:
        res = round_up(n,d)  
    else:
        res = round(n,d)
    return float(res)

Comments

-1

It's easy to use math.ceil function:

Python 2

import math

math.ceil(100.5)
> 101.0

Python 3

import math

mail.ceil(100.5)
> 101

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.