배열의 중간을 계산할 때 왜 start + (end-start) / 2 over (start + end) / 2를 선호합니까?


160

프로그래머가 수식을 사용하는 것을 보았습니다.

mid = start + (end - start) / 2

더 간단한 공식을 사용하는 대신

mid = (start + end) / 2

배열 또는 목록에서 중간 요소를 찾는 데 사용됩니다.

그들은 왜 전자를 사용합니까?


51
거친 추측 : (start + end)넘칠 (end - start)수는 있지만 넘칠 수는 없습니다.
cadaniluk

30
후자 때문에 작업 할 때하지 않습니다 startend포인터입니다.
ensc


20
start + (end - start) / 2또한 의미 론적 의미를 지니고 있습니다. (end - start)길이 start + half the length입니다.
njzk2 2

2
@ LưuVĩnhPhúc :이 질문에 최고의 답변과 가장 많은 표가 없습니까? 그렇다면 다른 질문은 아마도이 질문으로 종결되어야합니다. 게시물의 나이는 관련이 없습니다.
Nisse Engström

답변:


218

세 가지 이유가 있습니다.

우선, 1을 오버플 start + (end - start) / 2end - 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하지 오버 플로우 경우 것입니다 startend큰 긍정적 인 숫자입니다. 부호있는 피연산자를 사용하면 오버플로가 정의되지 않습니다.

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는 전체 주소 공간의 절반 이상을 사용하여 배열을 할당해야 합니다.


(end - start)signed int경우 결과는 오버플로 될 때 정의되지 않습니다.
ensc

end-start오버플로되지 않음 을 증명할 수 있습니까 ? AFAIK 네거티브 start를 사용하면 오버플로가 발생할 수 있습니다. 물론, 평균을 계산할 때 대부분의 경우 값은 다음과 같습니다 >= 0.
Bakuriu

12
@Bakuriu : 사실이 아닌 것을 증명하는 것은 불가능합니다.
Dietrich Epp

4
포인터 뺄셈은 (표준에 따라) 의도적으로 설계 되었기 때문에 C에 특히 관심이 있습니다. 구현은 end - start객체 크기는 서명되지 않은 반면 포인터 차이는 서명되므로 정의되지 않은 배열을 너무 크게 만들 수 있습니다. 따라서 end - start배열의 크기를 어떻게 든 아래에 유지한다면 "포인터를 사용하더라도 작동합니다" PTRDIFF_MAX. 표준에 공평하게 말하면, 메모리 맵 크기의 절반이기 때문에 대부분의 아키텍처에 방해가되지 않습니다.
Steve Jessop

3
@Bakuriu : 그건 그렇고, 내가 뭔가를 놓쳤다 고 생각하거나 불분명하다고 생각되는 경우 게시물에 "편집"버튼을 사용하여 변경을 제안하거나 직접 만들 수 있습니다. 나는 인간 일 뿐이다.이 포스트는 2 천쌍 이상의 안구에 의해 보여졌다. "당신이 명확히해야한다 ..."라는 말은 실제로 나를 잘못된 길로 인도합니다.
Dietrich Epp

18

이 사실을 보여주기 위해 간단한 예를 들어 보겠습니다. 특정 배열에서 range의 중간 점을 찾으려고 한다고 가정 합니다 [1000, INT_MAX]. 이제 데이터 유형이 저장할 수 INT_MAX있는 가장 큰 값 int입니다. 경우에도 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) 있는 오버 플로우하지 않습니다 .


1
에 1을 더하면 INT_MAX결과는 음수가 아니라 정의되지 않습니다.
celtschk

@celtschk 이론적으로는 그렇습니다. 실제로 많은 시간이에서 INT_MAX로 바뀔 것입니다 -INT_MAX. 그러나 그것에 의존하는 것은 나쁜 습관입니다.
Mast

17

다른 사람들이 이미 말한 것을 더하기 위해 첫 번째 것은 수학적으로 덜 생각하는 사람들에게 그 의미를 명확하게 설명합니다.

mid = start + (end - start) / 2

다음과 같이 읽습니다.

중간은 시작에 길이의 절반을 더한 것과 같습니다.

이므로:

mid = (start + end) / 2

다음과 같이 읽습니다.

중간은 시작 + 끝의 절반과 같습니다.

적어도 그렇게 표현할 때 첫 번째만큼 명확하지 않은 것 같습니다.

Kos가 지적했듯이 다음과 같이 읽을 수도 있습니다.

중간은 시작과 끝의 평균과 같습니다.

적어도 내 의견으로는 첫 번째만큼 분명하지만 명확하지는 않습니다.


3
나는 당신의 요점을 본다. 그러나 이것은 정말로 스트레칭이다. "e-s"가 표시되고 "길이"라고 생각하면 거의 확실하게 "(s + e) ​​/ 2"가 표시되고 "평균"또는 "중간"으로 생각됩니다.
djechlin

2
@djechlin 프로그래머는 수학을 잘 못합니다. 그들은 일을 하느라 바쁩니다. 그들은 수학 수업에 참석할 시간이 없습니다.
Little Alien

1

start + (end-start) / 2는 가능한 오버플로를 피할 수 있습니다 (예 : start = 2 ^ 20 및 end = 2 ^ 30).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.