我见过程序员使用公式
mid = start + (end - start) / 2
而不是使用更简单的公式
mid = (start + end) / 2
用于查找数组或列表中的中间元素。
为什么他们使用前一个?
答案 0 :(得分:214)
有三个原因。
首先,只要start + (end - start) / 2
不溢出 1 ,即使您使用指针,end - start
仍然有效。
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) / 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 + (end - start) / 2
永远不会溢出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!
1 根据C标准,如果指针减法的结果不能表示为ptrdiff_t
,则行为未定义。但是,实际上,这需要使用至少一半的整个地址空间来分配char
数组。
答案 1 :(得分:17)
我们可以举一个简单的例子来证明这一事实。假设在某个大数组中,我们试图找到范围[1000, INT_MAX]
的中点。现在,INT_MAX
是int
数据类型可以存储的最大值。即使添加了1
,最终值也会变为负值。
此外,start = 1000
和end = INT_MAX
。
使用公式:(start + end)/2
,
中点将是
如果我们尝试使用此值进行索引,则
(1000 + INT_MAX)/2
=-(INT_MAX+999)/2
否定且可能会出现分段错误。
但是,使用公式(start + (end-start)/2)
,我们得到:
(1000 + (INT_MAX-1000)/2)
=(1000 + INT_MAX/2 - 500)
=(INT_MAX/2 + 500)
不会溢出。
答案 2 :(得分:16)
为了补充其他人已经说过的内容,第一个解释了它对于那些数学意义较小的人的含义更清楚:
mid = start + (end - start) / 2
读作:
中等于开始加上一半的长度。
,而:
mid = (start + end) / 2
读作:
中等于开始加上结束的一半
这似乎不像第一个那样清晰,至少在表达时如此。
科斯指出它也可以读作:mid等于开始和结束的平均值
哪个更清楚,但至少在我看来,仍然没有,与第一个一样清楚。
答案 3 :(得分:0)
start +(end-start)/ 2可以避免可能的溢出,例如start = 2 ^ 20和end = 2 ^ 30