如何正确舍入半浮点数?
How to properly round-up half float numbers?
我遇到了 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
但结果是一样的。
四舍五入到最接近的偶数已成为数值学科中的常见做法。 "Rounding up" 会略微偏向较大的结果。
因此,从科学机构的角度来看,round
具有正确的行为。
简短版本:使用 decimal module。它可以精确地表示像 2.675 这样的数字,不像 Python 浮点数,其中 2.675 实际上是 2.67499999999999982236431605997495353221893310546875(准确)。您可以指定所需的舍入:ROUND_CEILING、ROUND_DOWN、ROUND_FLOOR、ROUND_HALF_DOWN、ROUND_HALF_EVEN、ROUND_HALF_UP、ROUND_UP , 和 ROUND_05UP 都是选项。
Numeric Types section 明确记录了此行为:
round(x[, n])
x rounded to n digits, rounding half to even. If n is omitted, it defaults to 0.
请注意 四舍五入为偶数。这也叫bankers rounding;不是总是向上或向下舍入(复合舍入误差),而是通过舍入到最接近的 even 数字来平均舍入误差。
如果您需要更好地控制舍入行为,请使用 decimal
module, which lets you specify exactly what rounding strategy should be used。
例如,从一半向上取整:
>>> 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
您看到的行为是典型的 IEEE 754 舍入行为。如果它必须在两个与输入的差异相等的数字之间进行选择,它总是会选择偶数。这种行为的优点是平均舍入效果为零 - 同样多的数字向上和向下舍入。如果以一致的方向舍入中途数字,舍入将影响预期值。
如果 objective 是公平舍入,那么您看到的行为是正确的,但这并不总是需要的。
获得所需舍入类型的一个技巧是添加 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)
您可以使用:
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.0
和 0.49999999999999994
。
喜欢 fedor2612 的回答。我用一个可选的“小数”参数扩展了它,供那些想要使用此函数四舍五入任意小数位数的人使用(例如,如果你想将 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
这是另一种解决方案。
它将在 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
模块(结果很慢)的情况下实现了 "school fashion rounding"。
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+1
的过程中将始终是 x.5 + 0.00...01
,因此 round 函数将正常工作并且它会舍入 x.5
到 x+1
在这个问题中,这基本上是一个正整数除以 2 的问题。最简单的方法是 int(n + 0.5)
对于单个数字。
但是我们不能将其应用于系列,因此我们可以在不进入循环的情况下为 pandas 数据帧做的是:
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=10
和 int(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
一些测试:
yw
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
所以为了确保这里有一个crystal清晰的工作示例,我写了一个小的方便函数
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:
"""
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
在某些情况下,部分解决方案的四舍五入可能无法按预期进行。
例如使用上面的函数:
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)
我遇到了 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
但结果是一样的。
四舍五入到最接近的偶数已成为数值学科中的常见做法。 "Rounding up" 会略微偏向较大的结果。
因此,从科学机构的角度来看,round
具有正确的行为。
简短版本:使用 decimal module。它可以精确地表示像 2.675 这样的数字,不像 Python 浮点数,其中 2.675 实际上是 2.67499999999999982236431605997495353221893310546875(准确)。您可以指定所需的舍入:ROUND_CEILING、ROUND_DOWN、ROUND_FLOOR、ROUND_HALF_DOWN、ROUND_HALF_EVEN、ROUND_HALF_UP、ROUND_UP , 和 ROUND_05UP 都是选项。
Numeric Types section 明确记录了此行为:
round(x[, n])
x rounded to n digits, rounding half to even. If n is omitted, it defaults to 0.
请注意 四舍五入为偶数。这也叫bankers rounding;不是总是向上或向下舍入(复合舍入误差),而是通过舍入到最接近的 even 数字来平均舍入误差。
如果您需要更好地控制舍入行为,请使用 decimal
module, which lets you specify exactly what rounding strategy should be used。
例如,从一半向上取整:
>>> 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
您看到的行为是典型的 IEEE 754 舍入行为。如果它必须在两个与输入的差异相等的数字之间进行选择,它总是会选择偶数。这种行为的优点是平均舍入效果为零 - 同样多的数字向上和向下舍入。如果以一致的方向舍入中途数字,舍入将影响预期值。
如果 objective 是公平舍入,那么您看到的行为是正确的,但这并不总是需要的。
获得所需舍入类型的一个技巧是添加 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)
您可以使用:
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.0
和 0.49999999999999994
。
喜欢 fedor2612 的回答。我用一个可选的“小数”参数扩展了它,供那些想要使用此函数四舍五入任意小数位数的人使用(例如,如果你想将 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
这是另一种解决方案。 它将在 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
模块(结果很慢)的情况下实现了 "school fashion rounding"。
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+1
的过程中将始终是 x.5 + 0.00...01
,因此 round 函数将正常工作并且它会舍入 x.5
到 x+1
在这个问题中,这基本上是一个正整数除以 2 的问题。最简单的方法是 int(n + 0.5)
对于单个数字。
但是我们不能将其应用于系列,因此我们可以在不进入循环的情况下为 pandas 数据帧做的是:
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=10
和 int(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
一些测试:
yw
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
所以为了确保这里有一个crystal清晰的工作示例,我写了一个小的方便函数
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:
"""
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
在某些情况下,部分解决方案的四舍五入可能无法按预期进行。
例如使用上面的函数:
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)