为什么在计算数组的中间时更喜欢start +(end-start)/ 2 over(start + end)/ 2?

时间:2016-07-31 20:13:51

标签: c algorithm

我见过程序员使用公式

mid = start + (end - start) / 2

而不是使用更简单的公式

mid = (start + end) / 2

用于查找数组或列表中的中间元素。

为什么他们使用前一个?

4 个答案:

答案 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) / 2start为大正数,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 < 0end < 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_MAXint数据类型可以存储的最大值。即使添加了1,最终值也会变为负值。

此外,start = 1000end = 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