두 정수의 평균을 구하는 가장 좋은 방법은 무엇일까요?

시지프스의 이미지

물론, 프로그래밍 언어에서 계산하는 경우를 말합니다.

사실
http://googleresearch.blogspot.com/2006/06/extra-extra-read-all-about-it-nearly.html
이 글을 읽다가 문득 궁금해진 내용입니다.
양수, 음수 포함하여 모든 정수를 고려할 때, 어떤 방법이 가장 좋을까요?
아래 나열한 방법은 전부 위의 링크에서 가져와서, 제 생각을 덧붙인 것입니다.

가장 무식한 방법은
mid = (low + high) / 2;
입니다.
그런데 이 방법은 overflow의 문제가 있습니다.

그 다음 방법은
mid = low + (high - low) / 2;
입니다.
이 방법은 0 <= low <= high일 때는 잘 작동하지만,
high = 0, low = 1이 되면 (-1) / 2가 계산되면서 문제가 되고,
high = 0, low = INT_MIN이 되면 overflow가 발생합니다.

또 다른 방법은
mid = (((unsigned int) low) + ((unsigned int) high)) / 2;
입니다.
그런데, 이 방법도 문제가 있습니다.
원래 low, high가 음수였을 경우엔 오답이 나옵니다.
그리고 양수로 한정하더라도 INT_MAX * 2 <= UINT_MAX가 보장이 되는지는 의문입니다. (임의의 언어, 임의의 환경을 고려할 때 말입니다.)

또 다른 방법은
mid = (low / 2) + (high / 2) + (((low % 2) + (high % 2)) / 2);
입니다.
아마, X == ((X / 2) * 2) + (X % 2) for all X 정도의 조건이 필요한데, (아마도) 요새 언어에서 대부분 이 조건은 만족하니까 상관 없습니다.
그외에 필요한 조건은 발견하지 못했습니다.
다만, 연산의 수가 너무 많아서 사용하고 싶은 마음이 안 듭니다.
(이론적으로 /와 %는 연산 한 번에 될 듯 싶지만, 그런 명령어가 있는지 모르겠습니다. 그래서 연산이 많습니다.)

또 다른 방법은
mid = (low / 2) + (high / 2) + (low & high & 1);
입니다.
바로 위의 방법과 비슷한 접근입니다.
고민해본 결과, (-3) / 2를 -1로 계산하는 C에서는 오답이 나옵니다. (-3) / 2를 -2로 계산하면 (아마도) 언제나 정답인 것 같습니다.
gcc는 >>를 arithmetic shift로 계산하므로 써 먹을 수 있을 것 같습니다.

또 다른 방법은
mid = (low & high) + (low ^ high) / 2;
입니다.
이건 아무리 생각해도 항상 정답이 나오는 것 같습니다.
(-3) / 2를 -2로 계산하든 -1로 계산하든 상관 없습니다.
(low ^ high)가 음수이고 평균값이 정수가 아닐 때, 정확히 중간에서 0.5만큼 왼쪽을 찍냐 오른쪽을 찍냐 차이입니다.
게다가 정답을 보장하는 방식 중 비교적 연산 횟수도 적어 보입니다.
(코멘트가 없으면 이해하기 어렵다는 것이 단점일까요.)

과연 더 좋은 방법이 있을까요?
마지막 방법은 좋은 코드일까요? 정말 모든 경우에 동작한다고 확신할 수 있을까요?

snowall의 이미지

이미 나온 연산을 비트연산으로 바꿔보면 좀 빨라질 것 같은데요
mid = (low / 2) + (high / 2) + (((low % 2) + (high % 2)) / 2);
이걸 다음과 같이 바꿔봅니다.

m = mid
l = low
h = high

m = (l>>1)+(h>>1) - (l-(l>>1)<<1)+(h - ((h>>1)<<1))>>1

뭐...연산 수는 같거나 더 많아보이네요 -_-;

피할 수 있을때 즐겨라! http://melotopia.net/b

kaeri17의 이미지

( low & 1 ) & (high & 1) 하면 되지 않나요?? 둘다 마지막비트가 1일때만 1이 나오게끔요..

익명 사용자의 이미지

low & high & 1

winner의 이미지

수치가 안 크면 첫번째...
두번째에서 overflow가 발생할 수 있다는 인식은 이제껏 못했네요. Bit 연산은 하고 싶지 않고, 4번째 방법을 쓸 바에는 두번재 방법에 if else 를 적용하겠습니다.
그리고 몫과 나머지를 동시에 구하는 것은 Assembly에서 가능하고 C 함수에도 있습니다. 구조체로 몫과 나머지가 동시에 반환되죠.

kornlid의 이미지

만만하게 보고 풀어보려 했는데 어렵네요..;;

익명 사용자의 이미지

오버플로우를 감안하고 있는것 자체가 특정 언어에 너무 의존적인 생각 아닌가 싶습니다.
c에서야 a+b가 경우에 따라 오버플로우가 날수 있겟지만
루비와 같이 큰 정수를 다루는 방법을 이미 내장한 언어의 경우는 오버플로우가 날래야 날수가 없죠.
(메모리가 변수가 되겟지만 말입니다.)

(그럴리도 없겟지만)극단적인 속도가 필요한 경우가 아니라면
가장 직관적인 방법인 (a+b)/2 와 같은 방법이 제일 좋은 방법 같습니다.
누가봐도 저건 두 값의 평균을 구하는 연산이니까요.

시지프스의 이미지

C나 C++같은 언어만 쓰는 경우도 있으니까요. 오버플로우를 언제나 무시할 수는 없는 것 같습니다.
물론, 좋은 언어를 쓰면 (a + b) / 2가 직관적으로 좋은 방법입니다.

정확성, 이식성, 성능, 가독성을 동시에 만족시키는 것은 어려운 문제인 것 같군요.
개인적으로 저라면,
mid = (low & high) + (low ^ high) >> 1;
mid = (low & high) + (low ^ high) / 2;
중 하나를 고르겠습니다. 특히
mid = (low & high) + (low ^ high) >> 1;
가 자세히 들여다 보면 참 잘 만든 코드네요.

다만, >>를 쓸 경우, C 언어 등 signed type에 대한 bit shift가 정의되지 않은 언어에서는, portability는 문제가 됩니다.

begin{signature}
THIS IS SPARTA!!!!!n.
end{signature}

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.