Сопряжение Python 3 в Python 2

В Python 2.x встроенный round имеет следующее поведение:

если два множителя одинаково близки, округление выполняется вдали от 0 (так, например, раунд (0.5) равен 1,0, а раунд (-0.5) равен -1.0)

В Python 3.x это изменилось на более распространенное:

если два мультипликатора одинаково близки, округление выполняется до четного выбора (например, как раунд (0.5), так и раунд (-0.5) равны 0, а раунд (1.5) равен 2).

Есть ли простой способ получить такое поведение в Python 2.x? К сожалению, модуль future_builtins не включает это. Может быть, есть еще один подобный модуль, который я еще не нашел? Или другой способ вытащить функции Python 3.x в Python 2.x?

Очевидно, я мог бы написать новую функцию, которая выдает желаемое поведение, но мне более любопытно, если существует решение, использующее реальную функцию Python 3.x, чтобы избежать добавления ненужной сложности и кода для обслуживания.

Ответы

Ответ 1

Если вы не учитываете зависимость numpy, numpy.around может сделать следующее:

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

Ответ 2

Python 3 round в Python 2

Функция может выглядеть так:

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'

Позвольте мне пояснить, что делает раунд в bltinmodule.c

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

Итак, круглый фактически почти ничего не делает. Это зависит от переданных ему объектов. Это приводит к floatobject.c функции 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);

Я использовал эти строки в своей функции выше.

Python 2 round в Python 3

Думаю, вам нужно написать новую функцию.

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

Оператор if обрабатывает случай, когда i + 0.5 и i + 1.5 округляются в разные стороны = до четных чисел и половин. В этом случае округление выполняется от нуля.

# 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'

Вам нужно решение со вторым аргументом round, ndigits?

Ответ 3

Этот ответ прочитал исходный вопрос, и ответ "Нет, я не могу придумать что-то, что использует исходный код Py3".

Но для тех, кто задается вопросом, каким будет код, который будет реплицировать поведение Py3 в Py2 (включая поведение int против float), здесь приведена адаптация кода User выше, включающая ndigits и int vs. float для 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

Ответ 4

Если вы супер параноик по вопросам округления с плавающей точкой, вы можете заглянуть в библиотеку decimal, где вы можете настроить округление (по умолчанию - 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')

В противном случае я думаю, что он более явный и более удобный, если вы просто напишете свою собственную функцию или используете numpy, а не хотите, чтобы вы могли использовать функцию py3 в py2.

Ответ 5

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

Ответ 6

Используйте decimal.Decimal.to_integral_value

Например:

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

Варианты округления описаны здесь. Два варианта, которые нас интересуют:

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