web-dev-qa-db-ja.com

配列の中央を計算するときに、start +(end-start)/ 2よりも(start + end)/ 2を好むのはなぜですか?

プログラマーが式を使用するのを見てきました

mid = start + (end - start) / 2

より単純な式を使用する代わりに

mid = (start + end) / 2

配列またはリストの中央の要素を見つけるため。

なぜ前者を使用するのですか?

159
Pallavi Chauhan

3つの理由があります。

まず、end - startがオーバーフローしない限り、ポインターを使用していてもstart + (end - start) / 2は機能します1

int *start = ..., *end = ...;
int *mid = start + (end - start) / 2; // works as expected
int *mid = (start + end) / 2;         // type error, won't compile

第二に、startendが大きな正数の場合、start + (end - start) / 2はオーバーフローしません。符号付きオペランドでは、オーバーフローは未定義です。

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の場合のみです。)

または、符号なし算術では、オーバーフローが定義されていますが、間違った答えが返されます。ただし、符号なしオペランドの場合、end >= startである限り、start + (end - start) / 2はオーバーフローしません。

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配列を割り当てる必要があります。

217
Dietrich Epp

この事実を示すために簡単な例を挙げることができます。特定のlarge配列で、[1000, INT_MAX]の範囲の中点を見つけようとしているとします。現在、INT_MAXintデータ型が格納できる最大値です。これに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)オーバーフローしない

17
Shubham

他の人がすでに言ったことに追加するために、最初のものはその意味を数学的にそれほど気にしない人に明確に説明します:

mid = start + (end - start) / 2

次のように読み取ります:

midはstartに長さの半分を加えたものに等しい。

一方:

mid = (start + end) / 2

次のように読み取ります:

midは、開始と終了の半分に等しい

少なくともそのように表現されたとき、それは最初のものほど明確に見えません。

コスが指摘したように、それはまた読むことができます:

midは開始と終了の平均に等しい

少なくとも私の意見では、それは最初のものほど明確ですが、まだ明確ではありません。

16
TheLethalCoder

start +(end-start)/ 2は、可能なオーバーフローを回避できます。たとえば、start = 2 ^ 20およびend = 2 ^ 30

0
fight_club