Ответ 1
Если вы не учитываете зависимость numpy, numpy.around
может сделать следующее:
>>> from numpy import around
>>> around(0.5)
0
>>> around(-0.5)
-0
>>> around(1.5)
2.0
В 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, чтобы избежать добавления ненужной сложности и кода для обслуживания.
Если вы не учитываете зависимость numpy, numpy.around
может сделать следующее:
>>> from numpy import around
>>> around(0.5)
0
>>> around(-0.5)
-0
>>> around(1.5)
2.0
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
?
Этот ответ прочитал исходный вопрос, и ответ "Нет, я не могу придумать что-то, что использует исходный код 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
Если вы супер параноик по вопросам округления с плавающей точкой, вы можете заглянуть в библиотеку 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.
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
Используйте 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),