我听说,在计算平均值时,start+(end-start)/2 与 (start+end)/2 不同,因为后者会导致溢出。我不太明白为什么第二个会导致溢出,而第一个不会。实现可以避免溢出的数学公式的通用规则是什么。
4 回答
假设您使用的计算机的最大整数值为 10,并且您想要计算 5 和 7 的平均值。
第一种方法 (begin + (end-begin)/2) 给出
5 + (7-5)/2 == 5 + 2/2 == 6
第二种方法 (begin + end)/2 给出了溢出,因为中间的 12 值超过了我们接受的最大值 10 并“换行”到其他东西(如果您使用无符号数字,通常会换行到零,但如果你的数字是签名的,你可能会得到一个负数!)。
12/2 => overflow occurs => 2/2 == 1
当然,在实际计算机中,整数会以 2^32 而不是 10 这样的大值溢出,但想法是一样的。不幸的是,没有我所知道的摆脱溢出的“通用”方法,这在很大程度上取决于您使用的特定算法。然后事件,事情变得更加复杂。根据您在后台使用的数字类型,您可以获得不同的行为,除了上溢和下溢之外,还有其他类型的数字错误需要担心。
您的两个公式都会溢出,但在不同的情况下:
- 当并且都接近范围同一侧的整数限制时(即均为正数或均为负数)
(start+end)
,您的(start+end)/2
公式部分将溢出。start
end
- 当为正数和负数时,公式的
(end-start)
一部分将溢出,并且两个值都接近可表示整数值的各自末端。start+(end-start)/2
start
end
没有“通用”规则,您可以根据具体情况进行操作:查看公式的某些部分,考虑可能导致溢出的情况,并想出避免溢出的方法。例如,start+(end-start)/2
当您对具有相同符号的值进行平均时,可以显示公式以避免溢出。
这是艰难的道路;简单的方法是对中间结果使用更高容量的表示。例如,如果您使用long long
而不是int
进行中间计算并仅在完成后将结果复制回,int
则假设最终结果适合int
.
在处理整数时,您可能会在采用此类策略时关心整数溢出。
请注意,使用公式b+(b-a)/2
您要确保a <= b
. 否则,您可能会在可能值范围的下限处遇到相同的问题。想想a/2+b/2
。然而,这种方法也有其他缺点。
处理浮点数时会出现另一个问题,即灾难性取消。由于浮点表示的有效数字数量有限,当添加大量数字时,准确性会丢失(即使这只是一个中间步骤)。
为了解决这个数值稳定性问题,例如可以使用这个算法(稍微改编自维基百科):
def online_mean(data):
n = 0
mean = 0
for x in data:
n = n + 1
delta = x - mean
mean = mean + delta/n
return mean
我不知何故觉得与你上面提出的公式有关系......
在二分搜索中,我们将编写以下代码:
if(start > end){
return;
}
int mid = start + (end - start) / 2;
通过使用start + (end - start) / 2
,我们可以避免@dasblinkenlight 指出的问题
如果我们使用(start + end) / 2
,它会溢出,如 dasblinkenlight 所示