이번 문서의 목표: 이 파일을 다 읽으면 임의의 정수를 1의 보수·2의 보수로 직접 변환하고, 보수를 이용한 뺄셈을 비트 단위로 수행하며, 오버플로 발생 여부를 규칙으로 판정할 수 있다.
왜 컴퓨터는 뺄셈을 덧셈으로 바꿔서 할까
01편에서 2진법의 자리값과 이진수 덧셈·뺄셈의 “의미”를 다뤘다. 그런데 실제 CPU 내부의 ALU(Arithmetic Logic Unit, 산술논리연산장치)는 회로 하나로 덧셈과 뺄셈을 모두 처리하고 싶어한다. 덧셈 회로와 뺄셈 회로를 따로 만들면 하드웨어가 두 배로 필요하기 때문이다.
그래서 컴퓨터는 뺄셈을 음수를 더하는 덧셈으로 바꿔서 처리한다. 을 으로 바꾸면, 덧셈 회로 하나로 뺄셈까지 해결할 수 있다. 문제는 “음수를 2진수로 어떻게 표현할 것인가”인데, 이 질문에 대한 답이 바로 보수(complement)다.
부호 없는 수: 모든 비트가 크기를 나타낸다
쉽게 말하면: 부호 없는 수는 음수 개념 없이, 비트 전부를 크기(magnitude)를 나타내는 데만 쓰는 표현이다.
부호 없는 정수(unsigned integer)는 비트로 부터 까지의 수를 표현한다. 예를 들어 8비트 부호 없는 정수는 (십진수 0)부터 (십진수 255)까지, 즉 까지 표현한다.
십진수 을 8비트 2진수로 바꿔보자. 173을 2로 나눈 나머지를 거꾸로 읽는 방식을 쓴다.
- , , , ,
각 자리(비트, bit)에 해당 값이 있으면 1, 없으면 0을 채우면 다음과 같다.
| 자리값() | |||||||
|---|---|---|---|---|---|---|---|
| 1 | 0 | 1 | 0 | 1 | 1 | 0 | 1 |
따라서 이다.
부호화 표현의 세 가지 방식
쉽게 말하면: 음수를 2진수로 표현하는 세 가지 전통적인 방법이 있는데, 최상위 비트(MSB)를 부호로 쓰는 것은 같지만 나머지 비트를 다루는 규칙이 다르다.
컴퓨터가 음수를 표현하는 방법에는 역사적으로 세 가지가 있다. 모두 최상위 비트(Most Significant Bit, MSB)를 부호 비트(sign bit)로 쓴다는 공통점이 있다. 부호 비트가 0이면 양수, 1이면 음수다.
- 부호화 크기 표현(sign-magnitude): 부호 비트를 제외한 나머지 비트가 그대로 절댓값(크기)을 나타낸다.
- 1의 보수(1’s complement, one’s complement): 양수의 모든 비트를 반전(0↔1)해서 음수를 만든다.
- 2의 보수(2’s complement, two’s complement): 1의 보수에 1을 더해서 음수를 만든다. 현대 컴퓨터가 표준으로 채택한 방식이다.
1의 보수: 비트를 뒤집는다
쉽게 말하면: 양수의 0과 1을 모두 뒤집으면 그 수의 1의 보수(음수 표현)가 된다.
정의: 비트 수 의 1의 보수는 의 각 비트를 반전한 것이다. 즉 각 비트 를 로 바꾼다.
비트열로 직접 변환해보기: 의 1의 보수
8비트로 를 표현하면 다음과 같다.
부호 비트(맨 왼쪽)가 0이므로 양수임을 나타낸다. 이제 모든 비트를 반전한다.
| 원래(+25) | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 1 |
|---|---|---|---|---|---|---|---|---|
| 반전(1의 보수) | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 0 |
따라서 의 1의 보수 표현은 이다.
1의 보수의 함정: 0이 두 개 존재한다
1의 보수 방식의 가장 큰 문제는 0이 두 개라는 점이다. 의 모든 비트를 반전하면 이 된다. 즉 과 이 서로 다른 비트열로 존재하면서도 같은 값을 나타내야 하는 모순이 생긴다. 이 때문에 회로 설계와 비교 연산이 복잡해지고, 표현 가능한 값의 개수도 개가 아니라 개(0이 중복되므로)로 줄어든다. 이 문제를 해결한 것이 2의 보수다.
2의 보수: 현대 컴퓨터의 표준
쉽게 말하면: 1의 보수를 구한 뒤 맨 끝에 1을 더하면 2의 보수가 되고, 이 방식은 0이 하나뿐이라 회로가 단순해진다.
정의: 비트 수 의 2의 보수는 의 1의 보수에 1을 더한 값이다.
비트열로 직접 변환해보기: 의 2의 보수
앞서 구한 의 1의 보수 에 1을 더한다.
1 1 1 0 0 1 1 0 (1의 보수)
+ 1
-----------------
1 1 1 0 0 1 1 1맨 오른쪽 자리: . 나머지 자리는 그대로 내려온다. 따라서 의 2의 보수 표현은 이다.
검산: 2의 보수로 표현한 음수가 맞는지 확인하는 방법은, 그 값을 다시 2의 보수를 취해 원래 양수가 나오는지 보는 것이다. 의 1의 보수는 이고, 여기에 1을 더하면 가 나온다. 원래 값으로 돌아왔으므로 계산이 맞다.
2의 보수의 자릿수 계산 공식
비트에서 음수 ()의 2의 보수 비트열은 다음 식으로도 구할 수 있다.
- : 비트 수
- : 표현하려는 수의 절댓값
예를 들어 8비트에서 는 이고, 을 8비트 2진수로 바꾸면 이므로 가 되어 위에서 손으로 구한 값과 정확히 일치한다.
2의 보수가 0을 하나로 만드는 이유
의 1의 보수는 이고, 여기에 1을 더하면 자리올림이 끝까지 발생해 이 된다. 8비트 결과만 남기면(가장 앞의 자리올림은 버려짐) , 즉 다시 이 나온다. 음수 0이 따로 존재하지 않는다. 이 덕분에 2의 보수는 비트로 부터 까지, 총 개의 값을 하나도 중복 없이 표현한다.
정수 표현 범위 비교표
| 표현 방식 | 비트 표현 범위 | 8비트 예시 범위 | 0의 개수 |
|---|---|---|---|
| 부호 없는 정수 | 해당 없음 | ||
| 부호화 크기 | 2개(+0, −0) | ||
| 1의 보수 | 2개(+0, −0) | ||
| 2의 보수 | 1개 |
시험 함정: 8비트 2의 보수의 범위는 로 비대칭이다. 왜 은 있는데 은 없을까? 2의 보수는 0을 양수 쪽에 포함시키므로(0은 부호 비트가 0인 “양수”로 취급) 양수 쪽 자리가 하나 줄고, 대신 그 여유분이 음수 쪽으로 넘어가 까지 표현할 수 있게 된다. 실제로 은 2의 보수에서 을 나타낸다.
2의 보수를 이용한 뺄셈: 비트열로 전 과정
쉽게 말하면: 는 로 바꾸고, 는 의 2의 보수로 만든 뒤 그냥 덧셈 회로에 넣으면 된다.
예제: 8비트로 계산하기
1단계: 를 8비트 2진수로 표현한다.
2단계: 를 8비트 2진수로 표현한다.
3단계: 의 2의 보수(즉 )를 구한다.
의 1의 보수는 (전 비트 반전)이고, 여기에 1을 더한다.
1 1 1 0 1 1 0 0
+ 1
-----------------
1 1 1 0 1 1 0 1따라서 .
4단계: 를 비트 단위로 덧셈한다.
0 0 1 1 0 1 0 0 (52)
+ 1 1 1 0 1 1 0 1 (-19)
-------------------
1 0 0 1 0 0 0 0 1오른쪽부터 한 자리씩 더한다.
| 자리(오른쪽부터) | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| 52의 비트 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | 0 |
| −19의 비트 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 1 |
| 자리올림(carry in) | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 1 |
| 합(비트) | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| 자리올림(carry out) | 0 | 0 | 1 | 1 | 0 | 1 | 1 | 1 |
8비트를 넘어가는 맨 앞의 자리올림(carry out) 1은 버린다(discard). 8비트만 남기면 결과는 이다.
5단계: 결과를 10진수로 검산한다.
. 실제로 이므로 정확히 일치한다.
2의 보수 뺄셈에서 최상위 자리에서 나가는 자리올림(맨 앞의 carry out)은 오버플로와 무관하게 항상 버린다. 이 자리올림을 오버플로 여부로 착각하는 것이 가장 흔한 실수다. 오버플로 판정은 다음 절의 별도 규칙을 따른다.
오버플로 판정: 부호 비트로 확인하는 두 가지 규칙
쉽게 말하면: 오버플로는 “정답이 그 비트 수로 표현 가능한 범위를 벗어났다”는 뜻이며, 부호 비트만 보고도 판정할 수 있다.
오버플로(overflow)란 연산 결과가 주어진 비트 수로 표현할 수 있는 범위를 벗어나는 현상이다. 8비트 2의 보수의 표현 범위는 이므로, 계산 결과가 이 범위를 벗어나면 오버플로가 발생한 것이다.
규칙 1: 부호 비트로 직접 판정하기
두 수를 더할 때, 두 피연산자의 부호가 같은데 결과의 부호가 달라지면 오버플로다. 부호가 다른 두 수를 더하는 경우(즉 뺄셈이 되는 경우)에는 오버플로가 절대 발생하지 않는다. 왜냐하면 서로 다른 부호의 두 수를 더한 결과는 항상 둘 중 더 큰 절댓값 범위 안에 들어오기 때문이다.
| 피연산자 A의 부호 | 피연산자 B의 부호 | 결과의 부호 | 오버플로 여부 |
|---|---|---|---|
| 양수(+) | 양수(+) | 음수(−)로 나옴 | 발생 |
| 음수(−) | 음수(−) | 양수(+)로 나옴 | 발생 |
| 양수(+) | 양수(+) | 양수(+)로 나옴 | 없음 |
| 음수(−) | 음수(−) | 음수(−)로 나옴 | 없음 |
| 서로 다름 | 서로 다름 | 무엇이든 | 없음 |
규칙 2: 최상위 두 자리의 자리올림 비교
더 형식적인 방법은 최상위 비트(부호 비트)로 들어오는 자리올림(carry in)과 최상위 비트에서 나가는 자리올림(carry out)을 비교하는 것이다.
- : 부호 비트(맨 왼쪽 자리)로 들어오는 자리올림
- : 부호 비트에서 밖으로 나가는 자리올림
- : XOR(배타적 논리합) 기호로, 두 값이 다르면 1(참), 같으면 0(거짓)
과 이 서로 다르면(하나는 0, 하나는 1이면) 오버플로가 발생한 것이고, 같으면(둘 다 0이거나 둘 다 1이면) 오버플로가 아니다.
오버플로 예제: 8비트로
, 이다. 둘 다 양수이므로 부호 비트는 둘 다 0이다.
0 1 1 0 0 1 0 0 (100)
+ 0 0 1 1 0 0 1 0 (50)
-------------------
1 0 0 1 0 0 1 1 0자리올림을 자리별로 추적하면 다음과 같다(오른쪽 1번 자리부터).
| 자리 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8(부호) |
|---|---|---|---|---|---|---|---|---|
| 100의 비트 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 0 |
| 50의 비트 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 0 |
| carry in | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 |
| 합 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 1 |
| carry out | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 |
부호 비트(8번째 자리) 계산을 보면 (7번째 자리에서 넘어온 자리올림)이고, 8번째 자리 계산 결과 (더 앞으로 넘어가는 자리올림 없음)이다. 이므로 오버플로가 발생했다.
결과 비트열은 부호 비트 1을 포함해 인데, 이는 8비트 2의 보수에서 음수로 해석된다. 실제로 은 8비트 2의 보수의 최댓값 을 넘으므로, 표현 범위를 벗어나 음수처럼 보이는 잘못된 결과가 나온 것이다. 이것이 오버플로의 실체다: 두 양수를 더했는데 결과가 음수로 나오는 모순이 규칙 1과 정확히 일치한다.
자주 틀리는 점
- 자리올림(carry)과 오버플로(overflow)를 같은 것으로 착각하는 것이 가장 흔한 실수다. 부호 없는 수 연산에서는 최상위 자리에서 나가는 자리올림 자체가 “범위 초과”를 뜻하지만, 2의 보수(부호 있는 수) 연산에서는 그 자리올림은 그냥 버리고 을 따로 확인해야 한다.
- 서로 부호가 다른 두 수를 더하는 경우(즉 실질적으로 뺄셈인 경우)는 오버플로가 원천적으로 발생하지 않는다는 점을 놓치면 안 된다.
핵심 정리
- 1의 보수는 전 비트 반전, 2의 보수는 1의 보수에 1을 더해 만들며, 2의 보수는 0이 하나뿐이라 현대 컴퓨터의 표준이다.
- 비트 2의 보수의 표현 범위는 로 비대칭이다(8비트: ).
- 뺄셈은 감수를 2의 보수로 바꾼 뒤 덧셈으로 처리하며, 결과의 최상위 자리에서 넘치는 자리올림은 버린다.
- 오버플로는 “두 피연산자의 부호가 같은데 결과 부호가 다르면 발생”, 형식적으로는 일 때 발생한다.
- 부호가 다른 두 수를 더하는 연산에서는 오버플로가 절대 발생하지 않는다.