[CS] 왜 음수를 표현할 때 2의 보수를 사용할까?

왜 대부분의 프로그래밍 언어는 2의 보수를 사용하는지 추론해보자

시작하며

최근에 kotlin으로 Spring boot 프로젝트를 제작하고 있던 중에, SHA-512를 사용해야 했었다. 방법을 찾아보니, 굉장히 특이한 코드가 보였다

Integer.toString((digest[i] and 0xff.toByte()) + 0x100, 16).substring(1)

해당 코드는 SHA-512를 거쳐서 나온 byte를 string으로 변경하는 코드인데, 왜 이렇게 쓰는 지 궁금해졌다.

그래서 파고파고 공부하다보니, 어느새 2의 보수까지 오게 되었다. 2의 보수를 다시 한번 공부하다보니, 음수를 표현할 때 2의 보수를 왜 사용했는 지에 대해 알 것 같았다. 그래서 해당 내용을 정리하고자 한다.

해당 내용은 필자의 주관적인 견해가 많이 포함되어 있습니다.

보수란?

보수(補數)는 보충을 해주는 수를 의미한다. 이를테면 1에 대한 10의 보수는 9, 4에 대한 15의 보수는 11의 개념이다. 1에 대한 2의 보수는 1이다.
출처 - 위키피디아

조금 더 보충 설명하자면, 보수는 각 자리의 수가 특정 수가 되게끔 보충해주는 수이다. 1에 대한 10의 보수는 9이고, 11에 대한 10의 보수는 89이다.

2의 보수란?

이진법에서 2의 보수를 계산해보자. $1_2$에 2의보수를 하면 $1_2$이고, $10_2$에 2의 보수를 하면 $10_2$이다. $11_2$에 2의 보수를 하면 $1_2$일 것이다.

이진법에서 1의 보수는 바로 이진수를 토글한 것이다. 여기에 +1을하면 2의 보수를 바로 구할 수 있다.

예시) $0011\ 0110_2 => 1100\ 1001_2 => 1100\ 1010_2$

토글이란?
이진수에서 0은 1로, 1은 0으로 변경하는 연산을 말한다. 각 비트에 not 연산을 한 것과 같다. 그래서 이진법에서 1의 보수는 매우 간단한 연산이다.

n진법일 때 n의 보수 쉽게 구하기
n-1의 보수를 구해서 +1을 하면 n의 보수를 바로 구할 수 있다.

이진수 토글의 특징

이진수를 토글한 후의 값은 특정 수식을 따른다. 어려운 수식은 아니고 정말 간단한 수식이다. 사실 보수의 특징을 생각한다면 쉽게 생각할 수 있다.

$\{비트\ 수에\ 맞는\ 최대값\}-\{기존값\} = \{토글된\ 값\}$

$0011\ 0110_2$을 토글하면 $1100\ 1001_2$이 된다. 십진수로 표현하면 54 -> 201가 된 것이다. 이를 다시 수식에 한번 대입해보자.

  1. 1 byte에서 최대값 255
  2. $0011\ 0110_2$은 십진수로 54
  3. 255 - 54 = 201
  4. 토글된 값은 $1100\ 1001_2$이고 이 값은 십진수로 201임
  5. 3번과 4번의 값이 같으므로 수식이 성립함

1byte로 표현되는 음수의 범위

1byte로 표현되는 양수의 범위는 0~255($2^8$ = 256개 만큼)인 것은 다들 잘 알고 있을 것이다. 그렇다면 음수는 어떻게 표현할까?

  • 양수 범위 : $0000\ 0000_2$ ~ $0111\ 1111_2$ ($2^7$ = 128개)
  • 음수 범위 : $1000\ 0000_2$ ~ $1111\ 1111_2$ ($2^7$ = 128개)

양수와 음수를 가르는 결정적인 차이는 최상위 비트이다. 최상위 비트가 0이면 양수, 1이면 음수로 표현하는 것이다.

그렇다면, 왜 최상위 비트로 결정하는 것일까? 그 이유는 표현 가능한 수의 개수를 정확히 2등분 하기 때문이다.

양수와 음수를 범위를 맞춤으로써 계산하기 쉽게 하기 위함이다.

최상위 비트가 1인 이진수는 어떻게 음수로 사용하는가?

$1000\ 0000_2$은 128인데, 이 숫자를 대체 어떻게 음수로 사용할 수 있을까? 여기서 이진수 토글이 등장한다.

앞서 이진수 토글은 $\{비트\ 수에\ 맞는\ 최대값\}-\{기존값\} = \{토글된\ 값\}$ 가 성립하는 특징을 가진다고 했다.

그렇다면 최상위 비트가 1인 친구들을 토글해보자.

  • 128 -> 127
  • 129 -> 126
  • 130 -> 125
  • 253 -> 2
  • 254 -> 1
  • 255 -> 0

어떤가? 기존 값이 증가할 수록 토글된 값은 감소하는 것을 볼 수 있다. 여기에 -를 붙인다면? 음수로 사용해도 되지 않겠는가?

이진수 토글을 음수로 사용할 경우 문제

앞서 진행한 과정을 보면 완벽해보인다. 하지만, 문제가 딱 한가지 있다. 그것은 쓸모없는 값이 하나 존재한다는 것이다.

최상위 비트가 1인 수를 이진수 토글을 통해 음수로 표현하게 되면 $-127…-0,0…127$와 같은 범위를 가지게 된다.

어라, -0이라는 값이 등장했다. 255를 이진수 토글하면 0이고 여기에 -를 붙이기로 했으니 -0이 나오기는 한다. 그런데 이는 0과 중복되는 의미를 가진다. 즉, 쓸모없는 데이터가 존재한다는 것이다.

+1을 해서 해결해보자

응당 개발자라면 이런 값은 눈 뜨고도 쳐다볼 수 없어야한다.(필자는 좀 그렇다…) 그렇다면 이진수 토글 이후 +1을 하면 해결되지 않겠는가?

  • 128 -> 128
  • 129 -> 127
  • 130 -> 126
  • 253 -> 3
  • 254 -> 2
  • 255 -> 1

드디어 $-128…-1,0…127$ 와 같은 우리가 흔히 보는 범위가 완성되었다.