我见过程序员用这个公式
mid = start + (end - start) / 2
而不是使用更简单的公式
mid = (start + end) / 2
用于查找数组或列表中的中间元素。
他们为什么用前者?
有三个原因。
首先,只要 end - start不溢出 1,即使使用指针,start + (end - start) / 2也能正常工作。
end - start
start + (end - start) / 2
int *start = ..., *end = ...; int *mid = start + (end - start) / 2; // works as expected int *mid = (start + end) / 2; // type error, won't compile
其次,如果 start和 end是大的正数,start + (end - start) / 2不会溢出。对于带符号的操作数,溢出是未定义的:
start
end
int start = 0x7ffffffe, end = 0x7fffffff; int mid = start + (end - start) / 2; // works as expected int mid = (start + end) / 2; // overflow... undefined
(请注意,end - start可能会溢出,但仅限于 start < 0或 end < 0。)
start < 0
end < 0
或者使用无符号算术,定义了溢出,但给出了错误的答案。但是,对于无符号操作数,start + (end - start) / 2永远不会像 end >= start那样溢出。
end >= start
unsigned start = 0xfffffffeu, end = 0xffffffffu; unsigned mid = start + (end - start) / 2; // works as expected unsigned mid = (start + end) / 2; // mid = 0x7ffffffe
最后,您通常希望向 start元素四舍五入。
int start = -3, end = 0; int mid = start + (end - start) / 2; // -2, closer to start int mid = (start + end) / 2; // -1, surprise!
根据 C 标准,如果指针减法的结果不能表示为 ptrdiff_t,则该行为是未定义的。但是,在实践中,这需要使用至少一半的整个地址空间来分配 char数组。
ptrdiff_t
char
我们可以用一个简单的例子来证明这个事实。假设在某个 很大数组中,我们试图找到范围 [1000, INT_MAX]的中点。现在,INT_MAX是 int数据类型可以存储的最大值。即使将 1加到这里,最终的值也会变为负值。
[1000, INT_MAX]
INT_MAX
int
1
还有 start = 1000和 end = INT_MAX。
start = 1000
end = INT_MAX
使用 (start + end)/2公式,
(start + end)/2
中点是
(1000 + INT_MAX)/2 = -(INT_MAX+999)/2,如果我们尝试使用这个值进行索引,它就是 没有和 可能会有内存区段错误。
(1000 + INT_MAX)/2
-(INT_MAX+999)/2
但是,使用公式 (start + (end-start)/2),我们得到:
(start + (end-start)/2)
(1000 + (INT_MAX-1000)/2) = (1000 + INT_MAX/2 - 500) = (INT_MAX/2 + 500).
(1000 + (INT_MAX-1000)/2)
(1000 + INT_MAX/2 - 500)
(INT_MAX/2 + 500)
为了补充其他人已经说过的话,第一条对那些不太懂数学的人解释得更清楚:
内容如下:
Mid 等于 start 加上长度的一半。
而:
中等于开始加结束的一半
这似乎不像第一个那么清楚,至少在这样表达的时候。
正如科斯指出的那样:
中等于开始和结束的平均值
这一点很清楚,但至少在我看来,还不如第一点清楚。
Start + (end-start)/2可以避免可能的溢出,例如 start = 2 ^ 20和 end = 2 ^ 30