我听说,在计算平均值时,start+(end-start)/2与(start+end)/2不同,因为后者会导致溢出。我不太明白为什么第二个会导致溢出,而第一个则不然。实现可以避免溢出的数学公式的通用规则是什么?
假设您正在使用最大整数值为 10 的计算机,并且想要计算 5 和 7 的平均值。
第一种方法(开始+(结束-开始)/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)
部分将溢出。 - 当
start
为正数而end
为负数时,start+(end-start)/2
公式的(end-start)
部分将溢出,并且这两个值都接近可表示整数值的各自末端。
没有"通用"规则,您可以逐案进行:查看公式的某些部分,考虑可能导致溢出的情况,并想出避免它的方法。例如,可以显示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 所示