如何正确地四舍五入一半浮点数?

我正面临着 round()函数的一种奇怪行为:

for i in range(1, 15, 2):
n = i / 2
print(n, "=>", round(n))

这个代码显示:

0.5 => 0
1.5 => 2
2.5 => 2
3.5 => 4
4.5 => 4
5.5 => 6
6.5 => 6

我希望浮点值总是四舍五入,但是,它被四舍五入到最接近的偶数。

为什么会出现这种行为,以及怎样才能得到正确的结果?

我尝试使用 fractions,但结果是相同的。

99199 次浏览

四舍五入到最接近的偶数已成为常见的做法,在数学学科。“四舍五入”会产生对更大结果的轻微偏见。

因此,从科学建立的角度来看,round具有正确的行为。

简而言之: 使用 十进制模块十进制模块。它可以精确地表示像2.675这样的数字,不像 Python 浮点数,其中2.675是 真的2.67499999999999982236431605997495353221893310546875(精确地)。并且您可以指定您想要的舍入: ROUNDCEILING,ROUNDDOWN,ROUNDFLOOR,ROUNDHALFDOWN,ROUNDHALFEVEN,ROUNDHALFUP,ROMOND05UP 都是选项。

< em > 数字类型 部分明确记录了这种行为:

round(x[, n])
X 四舍五入为 n 位,四舍五入为偶数。如果忽略 n,则默认为0。

注意 四舍五入到偶数。这也被称为 四舍五入的银行家; 而不是总是向上舍入或向下舍入(复合舍入误差) ,通过舍入到最接近的 even数字,您可以对舍入误差进行平均。

如果您需要对舍入行为进行更多的控制,请使用 decimal模块,它允许您确切地指定什么是 应采用四舍五入策略

例如,从一半四舍五入:

>>> from decimal import localcontext, Decimal, ROUND_HALF_UP
>>> with localcontext() as ctx:
...     ctx.rounding = ROUND_HALF_UP
...     for i in range(1, 15, 2):
...         n = Decimal(i) / 2
...         print(n, '=>', n.to_integral_value())
...
0.5 => 1
1.5 => 2
2.5 => 3
3.5 => 4
4.5 => 5
5.5 => 6
6.5 => 7

您看到的行为是典型的 IEEE754舍入行为。如果它必须在两个与输入同样不同的数字之间进行选择,它总是选择偶数。这种行为的优点是,平均舍入效应为零-等于许多数字向上和向下舍入。如果你按照一致的方向四舍五入中间数,四舍五入将会影响预期值。

如果目标是公平舍入,那么您看到的行为是正确的,但这并不总是需要的。

获得您想要的四舍五入类型的一个技巧是添加0.5,然后采取地板。例如,将0.5加到2.5得到3,地板为3。

例如:

from decimal import Decimal, ROUND_HALF_UP


Decimal(1.5).quantize(0, ROUND_HALF_UP)


# This also works for rounding to the integer part:
Decimal(1.5).to_integral_value(rounding=ROUND_HALF_UP)

You can use:

from decimal import Decimal, ROUND_HALF_UP


for i in range(1, 15, 2):
n = i / 2
print(n, "=>", Decimal(str(n)).quantize(Decimal("1"), rounding=ROUND_HALF_UP))

你可以用这个:

import math
def normal_round(n):
if n - math.floor(n) < 0.5:
return math.floor(n)
return math.ceil(n)

它会适当地向上或向下舍入数字。

根据数字是偶数还是奇数,round()将向上或向下舍入。一个简单的四舍五入方法是:

int(num + 0.5)

如果你想让这个方法适用于负数,可以使用:

((num > 0) - (num < 0)) * int(abs(num) + 0.5)

注意,对于大数字或真正精确的数字(如 5000000000000001.00.49999999999999994) ,这可能会造成混乱。

我喜欢 Fedor 2612的答案。我用一个可选的“小数”参数对它进行了扩展,以满足那些希望使用该函数四舍五入任意数量的小数的人(例如,如果你想四舍五入一种货币 $26.455到 $26.46)。

import math


def normal_round(n, decimals=0):
expoN = n * 10 ** decimals
if abs(expoN) - abs(math.floor(expoN)) < 0.5:
return math.floor(expoN) / 10 ** decimals
return math.ceil(expoN) / 10 ** decimals


oldRounding = round(26.455,2)
newRounding = normal_round(26.455,2)


print(oldRounding)
print(newRounding)

产出:

26.45

26.46

还有一个办法。 It will work as normal rounding in excel.

from decimal import Decimal, getcontext, ROUND_HALF_UP


round_context = getcontext()
round_context.rounding = ROUND_HALF_UP


def c_round(x, digits, precision=5):
tmp = round(Decimal(x), precision)
return float(tmp.__round__(digits))

c_round(0.15, 1) -> 0.2, c_round(0.5, 0) -> 1

没有任何图书馆的经典数学舍入

def rd(x,y=0):
''' A classical mathematical rounding by Voznica '''
m = int('1'+'0'*y) # multiplier - how many positions to the right
q = x*m # shift to the right by multiplier
c = int(q) # new number
i = int( (q-c)*10 ) # indicator number on the right
if i >= 5:
c += 1
return c/m


Compare:


print( round(0.49), round(0.51), round(0.5), round(1.5), round(2.5), round(0.15,1))  # 0  1  0  2  2  0.1


print( rd(0.49), rd(0.51), rd(0.5), rd(1.5), rd(2.5), rd(0.15,1))  # 0  1  1  2  3  0.2

下面的解决方案在不使用 decimal模块的情况下实现了“学校时尚舍入”(结果发现它很慢)。

def school_round(a_in,n_in):
''' python uses "banking round; while this round 0.05 up" '''
if (a_in * 10 ** (n_in + 1)) % 10 == 5:
return round(a_in + 1 / 10 ** (n_in + 1), n_in)
else:
return round(a_in, n_in)

例如:。

print(round(0.005,2)) # 0
print(school_round(0.005,2)) #0.01

你可以试试这个

def round(num):
return round(num + 10**(-9))

它将工作,因为 num = x.5将始终是 x.5 + 0.00...01的过程中,其更接近于 x+1,因此,轮功能将正常工作,它将四舍五入 x.5x+1

在这个问题中,当正整数除以2时,这基本上是一个问题。最简单的方法是用 int(n + 0.5)表示单个数字。

然而,我们不能将其应用于系列,因此,我们可以做的,例如,熊猫数据框架,而不进入循环,是:

import numpy as np
df['rounded_division'] = np.where(df['some_integer'] % 2 == 0, round(df['some_integer']/2,0), round((df['some_integer']+1)/2,0))

知道 round(9.99,0)轮到 int=10int(9.99)轮到 int=9会带来成功:

目标: 根据 value提供越来越低的轮数

    def get_half_round_numers(self, value):
"""
Returns dict with upper_half_rn and lower_half_rn
:param value:
:return:
"""
hrns = {}
if not isinstance(value, float):
print("Error>Input is not a float. None return.")
return None


value = round(value,2)
whole = int(value) # Rounds 9.99 to 9
remainder = (value - whole) * 100


if remainder >= 51:
hrns['upper_half_rn'] = round(round(value,0),2)  # Rounds 9.99 to 10
hrns['lower_half_rn'] = round(round(value,0) - 0.5,2)
else:
hrns['lower_half_rn'] = round(int(value),2)
hrns['upper_half_rn'] = round(int(value) + 0.5,2)


return hrns

一些测试:

enter image description here

哎呀

import math
# round tossing n digits from the end
def my_round(n, toss=1):


def normal_round(n):
if isinstance(n, int):
return n
intn, dec = str(n).split(".")
if int(dec[-1]) >= 5:
if len(dec) == 1:
return math.ceil(n)
else:
return float(intn + "." + str(int(dec[:-1]) + 1))
else:
return float(intn + "." + dec[:-1])


while toss >= 1:
n = normal_round(n)
toss -= 1
return n




for n in [1.25, 7.3576, 30.56]:
print(my_round(n, 2))


1.0
7.36
31

So just to make sure there is a crystal clear working example here, I wrote a small convenience function

def round_half_up(x: float, num_decimals: int) -> float:
"""Use explicit ROUND HALF UP. See references, for an explanation.


This is the proper way to round, as taught in school.


Args:
x:
num_decimals:


Returns:
https://stackoverflow.com/questions/33019698/how-to-properly-round-up-half-float-numbers-in-python


"""


if num_decimals < 0:
raise ValueError("Num decimals needs to be at least 0.")
target_precision = "1." + "0" * num_decimals
rounded_x = float(Decimal(x).quantize(Decimal(target_precision), ROUND_HALF_UP))
return rounded_x

以及一组合适的测试用例

def test_round_half_up():
x = 1.5
y = round_half_up(x, 0)
assert y == 2.0


y = round_half_up(x, 1)
assert y == 1.5


x = 1.25
y = round_half_up(x, 1)
assert y == 1.3


y = round_half_up(x, 2)
assert y == 1.25


A small addition as the rounding half up with some of the solutions might not work as expected in some cases.

例如,使用上面的函数:

from decimal import Decimal, ROUND_HALF_UP
def round_half_up(x: float, num_decimals: int) -> float:
if num_decimals < 0:
raise ValueError("Num decimals needs to be at least 0.")
target_precision = "1." + "0" * num_decimals
rounded_x = float(Decimal(x).quantize(Decimal(target_precision), ROUND_HALF_UP))
return rounded_x
round_half_up(1.35, 1)
1.4
round_half_up(4.35, 1)
4.3

在我期待的地方 4.4。什么是我的诀窍是转换 x成一个字符串第一。

from decimal import Decimal, ROUND_HALF_UP
def round_half_up(x: float, num_decimals: int) -> float:
if num_decimals < 0:
raise ValueError("Num decimals needs to be at least 0.")
target_precision = "1." + "0" * num_decimals
rounded_x = float(Decimal(str(x)).quantize(Decimal(target_precision), ROUND_HALF_UP))
return rounded_x


round_half_up(4.35, 1)
4.4

为什么这么复杂? (只对正数有效)

def HalfRoundUp(value):
return int(value + 0.5)

你当然可以把它做成一个 lambda,它是:

HalfRoundUp = lambda value: int(value + 0.5)

Unfortunately, this simple answer doesn't work with negative numbers, but it can be fixed with the floor function from math: (This works for both positive and negative numbers too)

from math import floor
def HalfRoundUp(value):
floor(value + 0.5)
import math
def round_half_up(x: float) -> int:
if x < 0:
return math.trunc(x) if -x % 1 < 0.5 else math.floor(x)
else:
return math.trunc(x) if  x % 1 < 0.5 else math.ceil(x)

这甚至适用于像 0.499999999999999945000000000000001.0这样的角落病例。