我看过程序员使用公式
mid = start + (end - start) / 2
而不是使用更简单的公式
mid = (start + end) / 2
用于查找数组或列表中的中间元素。
他们为什么使用前者?
start
和end
作为指针时不起作用。
start + (end - start) / 2
也带有语义含义:(end - start)
是长度,因此表示:start + half the length
。
我看过程序员使用公式
mid = start + (end - start) / 2
而不是使用更简单的公式
mid = (start + end) / 2
用于查找数组或列表中的中间元素。
他们为什么使用前者?
start
和end
作为指针时不起作用。
start + (end - start) / 2
也带有语义含义:(end - start)
是长度,因此表示:start + half the length
。
Answers:
有三个原因。
首先,start + (end - start) / 2
即使您使用的是指针,只要end - start
不溢出1,它都可以工作。
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标准,如果指针减法的结果不能表示为a ptrdiff_t
,则该行为不确定。但是,实际上,这需要char
至少使用整个地址空间的一半来分配数组。
(end - start)
该signed int
情况的结果在溢出时是不确定的。
end-start
不会溢出吗?AFAIK如果采取负值start
,应该可以使其溢出。当然,在大多数情况下,计算平均值时,您会知道值是>= 0
...
end - start
无法定义的数组,因为对象大小是无符号的,而指针差异是有符号的。因此,end - start
“即使使用指针也可以工作”,只要您还以某种方式将数组的大小保持在下面PTRDIFF_MAX
。公平地说,对于大多数架构而言,这并不是什么障碍,因为它只有内存映射的一半。
我们可以举一个简单的例子来证明这一事实。假设在某个较大的数组中,我们试图找到range的中点[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)
不会溢出。
INT_MAX
,结果将不是负数,而是不确定的。
INT_MAX
到,它将绕过很多时间-INT_MAX
。依靠它是一个坏习惯。
除了其他人已经说过的内容外,第一个解释对于那些数学上不太懂的人也更清楚地解释了它的含义:
mid = start + (end - start) / 2
读为:
中间等于开始加上长度的一半。
而:
mid = (start + end) / 2
读为:
中等于开始加结束的一半
至少当这样表达时,它似乎并不像第一个那样清晰。
正如Kos指出的那样,它也可以读为:
中值等于开始和结束的平均值
至少在我看来,哪一个更清晰,但仍不如第一个清晰。
(start + end)
可能会溢出,而(end - start)
不会。