Skip to Content
독학사독학사 2단계컴퓨터구조07. 자료의 표현: 정수·보수·오버플로

이번 문서의 목표: 이 파일을 다 읽으면 임의의 정수를 1의 보수·2의 보수로 직접 변환하고, 보수를 이용한 뺄셈을 비트 단위로 수행하며, 오버플로 발생 여부를 규칙으로 판정할 수 있다.

왜 컴퓨터는 뺄셈을 덧셈으로 바꿔서 할까

01편에서 2진법의 자리값과 이진수 덧셈·뺄셈의 “의미”를 다뤘다. 그런데 실제 CPU 내부의 ALU(Arithmetic Logic Unit, 산술논리연산장치)는 회로 하나로 덧셈과 뺄셈을 모두 처리하고 싶어한다. 덧셈 회로와 뺄셈 회로를 따로 만들면 하드웨어가 두 배로 필요하기 때문이다.

그래서 컴퓨터는 뺄셈을 음수를 더하는 덧셈으로 바꿔서 처리한다. 737 - 37+(3)7 + (-3)으로 바꾸면, 덧셈 회로 하나로 뺄셈까지 해결할 수 있다. 문제는 “음수를 2진수로 어떻게 표현할 것인가”인데, 이 질문에 대한 답이 바로 보수(complement)다.

부호 없는 수: 모든 비트가 크기를 나타낸다

쉽게 말하면: 부호 없는 수는 음수 개념 없이, 비트 전부를 크기(magnitude)를 나타내는 데만 쓰는 표현이다.

부호 없는 정수(unsigned integer)는 nn비트로 00부터 2n12^n - 1까지의 수를 표현한다. 예를 들어 8비트 부호 없는 정수는 00000000200000000_2(십진수 0)부터 11111111211111111_2(십진수 255)까지, 즉 281=2552^8 - 1 = 255까지 표현한다.

십진수 173173을 8비트 2진수로 바꿔보자. 173을 2로 나눈 나머지를 거꾸로 읽는 방식을 쓴다.

173=128+32+8+4+1173 = 128 + 32 + 8 + 4 + 1
  • 128=27128 = 2^7, 32=2532 = 2^5, 8=238 = 2^3, 4=224 = 2^2, 1=201 = 2^0

각 자리(비트, bit)에 해당 값이 있으면 1, 없으면 0을 채우면 다음과 같다.

자리값(272^7)262^6252^5242^4232^3222^2212^1202^0
10101101

따라서 17310=101011012173_{10} = 10101101_2이다.

부호화 표현의 세 가지 방식

쉽게 말하면: 음수를 2진수로 표현하는 세 가지 전통적인 방법이 있는데, 최상위 비트(MSB)를 부호로 쓰는 것은 같지만 나머지 비트를 다루는 규칙이 다르다.

컴퓨터가 음수를 표현하는 방법에는 역사적으로 세 가지가 있다. 모두 최상위 비트(Most Significant Bit, MSB)를 부호 비트(sign bit)로 쓴다는 공통점이 있다. 부호 비트가 0이면 양수, 1이면 음수다.

  1. 부호화 크기 표현(sign-magnitude): 부호 비트를 제외한 나머지 비트가 그대로 절댓값(크기)을 나타낸다.
  2. 1의 보수(1’s complement, one’s complement): 양수의 모든 비트를 반전(0↔1)해서 음수를 만든다.
  3. 2의 보수(2’s complement, two’s complement): 1의 보수에 1을 더해서 음수를 만든다. 현대 컴퓨터가 표준으로 채택한 방식이다.

1의 보수: 비트를 뒤집는다

쉽게 말하면: 양수의 0과 1을 모두 뒤집으면 그 수의 1의 보수(음수 표현)가 된다.

정의: nn비트 수 XX의 1의 보수는 XX의 각 비트를 반전한 것이다. 즉 각 비트 xix_i1xi1 - x_i로 바꾼다.

비트열로 직접 변환해보기: +25+25의 1의 보수

8비트로 +25+25를 표현하면 다음과 같다.

25=16+8+1=00011001225 = 16 + 8 + 1 = 00011001_2

부호 비트(맨 왼쪽)가 0이므로 양수임을 나타낸다. 이제 모든 비트를 반전한다.

원래(+25)00011001
반전(1의 보수)11100110

따라서 25-25의 1의 보수 표현은 11100110211100110_2이다.

1의 보수의 함정: 0이 두 개 존재한다

1의 보수 방식의 가장 큰 문제는 0이 두 개라는 점이다. +0=000000002+0 = 00000000_2의 모든 비트를 반전하면 0=111111112-0 = 11111111_2이 된다. 즉 +0+00-0이 서로 다른 비트열로 존재하면서도 같은 값을 나타내야 하는 모순이 생긴다. 이 때문에 회로 설계와 비교 연산이 복잡해지고, 표현 가능한 값의 개수도 2n2^n개가 아니라 2n12^n - 1개(0이 중복되므로)로 줄어든다. 이 문제를 해결한 것이 2의 보수다.

2의 보수: 현대 컴퓨터의 표준

쉽게 말하면: 1의 보수를 구한 뒤 맨 끝에 1을 더하면 2의 보수가 되고, 이 방식은 0이 하나뿐이라 회로가 단순해진다.

정의: nn비트 수 XX의 2의 보수는 XX의 1의 보수에 1을 더한 값이다.

비트열로 직접 변환해보기: +25+25의 2의 보수

앞서 구한 +25+25의 1의 보수 11100110211100110_2에 1을 더한다.

1 1 1 0 0 1 1 0 (1의 보수) + 1 ----------------- 1 1 1 0 0 1 1 1

맨 오른쪽 자리: 0+1=10 + 1 = 1. 나머지 자리는 그대로 내려온다. 따라서 25-25의 2의 보수 표현은 11100111211100111_2이다.

검산: 2의 보수로 표현한 음수가 맞는지 확인하는 방법은, 그 값을 다시 2의 보수를 취해 원래 양수가 나오는지 보는 것이다. 11100111211100111_2의 1의 보수는 00011000200011000_2이고, 여기에 1을 더하면 000110012=2500011001_2 = 25가 나온다. 원래 값으로 돌아왔으므로 계산이 맞다.

2의 보수의 자릿수 계산 공식

nn비트에서 음수 X-X(X>0X > 0)의 2의 보수 비트열은 다음 식으로도 구할 수 있다.

2의 보수(X)=2nX2\text{의 보수}(-X) = 2^n - X
  • nn: 비트 수
  • XX: 표현하려는 수의 절댓값

예를 들어 8비트에서 25-252825=25625=2312^8 - 25 = 256 - 25 = 231이고, 231231을 8비트 2진수로 바꾸면 128+64+32+4+2+1=231128+64+32+4+2+1 = 231이므로 11100111211100111_2가 되어 위에서 손으로 구한 값과 정확히 일치한다.

2의 보수가 0을 하나로 만드는 이유

+0=000000002+0 = 00000000_2의 1의 보수는 11111111211111111_2이고, 여기에 1을 더하면 자리올림이 끝까지 발생해 1000000002100000000_2이 된다. 8비트 결과만 남기면(가장 앞의 자리올림은 버려짐) 00000000200000000_2, 즉 다시 +0+0이 나온다. 음수 0이 따로 존재하지 않는다. 이 덕분에 2의 보수는 nn비트로 2n1-2^{n-1}부터 2n112^{n-1}-1까지, 총 2n2^n개의 값을 하나도 중복 없이 표현한다.

정수 표현 범위 비교표

표현 방식nn비트 표현 범위8비트 예시 범위0의 개수
부호 없는 정수02n10 \sim 2^n - 102550 \sim 255해당 없음
부호화 크기(2n11)2n11-(2^{n-1}-1) \sim 2^{n-1}-1127127-127 \sim 1272개(+0, −0)
1의 보수(2n11)2n11-(2^{n-1}-1) \sim 2^{n-1}-1127127-127 \sim 1272개(+0, −0)
2의 보수2n12n11-2^{n-1} \sim 2^{n-1}-1128127-128 \sim 1271개

시험 함정: 8비트 2의 보수의 범위는 128127-128 \sim 127비대칭이다. 왜 128-128은 있는데 +128+128은 없을까? 2의 보수는 0을 양수 쪽에 포함시키므로(0은 부호 비트가 0인 “양수”로 취급) 양수 쪽 자리가 하나 줄고, 대신 그 여유분이 음수 쪽으로 넘어가 128-128까지 표현할 수 있게 된다. 실제로 10000000210000000_2은 2의 보수에서 128-128을 나타낸다.

2의 보수를 이용한 뺄셈: 비트열로 전 과정

쉽게 말하면: ABA - BA+(B)A + (-B)로 바꾸고, B-BBB의 2의 보수로 만든 뒤 그냥 덧셈 회로에 넣으면 된다.

예제: 8비트로 521952 - 19 계산하기

1단계: A=52A = 52를 8비트 2진수로 표현한다.

52=32+16+4=00110100252 = 32 + 16 + 4 = 00110100_2

2단계: B=19B = 19를 8비트 2진수로 표현한다.

19=16+2+1=00010011219 = 16 + 2 + 1 = 00010011_2

3단계: BB의 2의 보수(즉 19-19)를 구한다.

1919의 1의 보수는 11101100211101100_2(전 비트 반전)이고, 여기에 1을 더한다.

1 1 1 0 1 1 0 0 + 1 ----------------- 1 1 1 0 1 1 0 1

따라서 19=111011012-19 = 11101101_2.

4단계: A+(B)A + (-B)를 비트 단위로 덧셈한다.

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

오른쪽부터 한 자리씩 더한다.

자리(오른쪽부터)12345678
52의 비트00101100
−19의 비트10110111
자리올림(carry in)00011011
합(비트)10000100
자리올림(carry out)00110111

8비트를 넘어가는 맨 앞의 자리올림(carry out) 1은 버린다(discard). 8비트만 남기면 결과는 00100001200100001_2이다.

5단계: 결과를 10진수로 검산한다.

001000012=32+1=3300100001_2 = 32 + 1 = 33. 실제로 5219=3352 - 19 = 33이므로 정확히 일치한다.

2의 보수 뺄셈에서 최상위 자리에서 나가는 자리올림(맨 앞의 carry out)은 오버플로와 무관하게 항상 버린다. 이 자리올림을 오버플로 여부로 착각하는 것이 가장 흔한 실수다. 오버플로 판정은 다음 절의 별도 규칙을 따른다.

오버플로 판정: 부호 비트로 확인하는 두 가지 규칙

쉽게 말하면: 오버플로는 “정답이 그 비트 수로 표현 가능한 범위를 벗어났다”는 뜻이며, 부호 비트만 보고도 판정할 수 있다.

오버플로(overflow)란 연산 결과가 주어진 비트 수로 표현할 수 있는 범위를 벗어나는 현상이다. 8비트 2의 보수의 표현 범위는 128127-128 \sim 127이므로, 계산 결과가 이 범위를 벗어나면 오버플로가 발생한 것이다.

규칙 1: 부호 비트로 직접 판정하기

두 수를 더할 때, 두 피연산자의 부호가 같은데 결과의 부호가 달라지면 오버플로다. 부호가 다른 두 수를 더하는 경우(즉 뺄셈이 되는 경우)에는 오버플로가 절대 발생하지 않는다. 왜냐하면 서로 다른 부호의 두 수를 더한 결과는 항상 둘 중 더 큰 절댓값 범위 안에 들어오기 때문이다.

피연산자 A의 부호피연산자 B의 부호결과의 부호오버플로 여부
양수(+)양수(+)음수(−)로 나옴발생
음수(−)음수(−)양수(+)로 나옴발생
양수(+)양수(+)양수(+)로 나옴없음
음수(−)음수(−)음수(−)로 나옴없음
서로 다름서로 다름무엇이든없음

규칙 2: 최상위 두 자리의 자리올림 비교

더 형식적인 방법은 최상위 비트(부호 비트)로 들어오는 자리올림(carry in)과 최상위 비트에서 나가는 자리올림(carry out)을 비교하는 것이다.

오버플로=CinCout\text{오버플로} = C_{in} \oplus C_{out}
  • CinC_{in}: 부호 비트(맨 왼쪽 자리)로 들어오는 자리올림
  • CoutC_{out}: 부호 비트에서 밖으로 나가는 자리올림
  • \oplus: XOR(배타적 논리합) 기호로, 두 값이 다르면 1(참), 같으면 0(거짓)

CinC_{in}CoutC_{out}이 서로 다르면(하나는 0, 하나는 1이면) 오버플로가 발생한 것이고, 같으면(둘 다 0이거나 둘 다 1이면) 오버플로가 아니다.

오버플로 예제: 8비트로 100+50100 + 50

100=011001002100 = 01100100_2, 50=00110010250 = 00110010_2이다. 둘 다 양수이므로 부호 비트는 둘 다 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번 자리부터).

자리12345678(부호)
100의 비트00100110
50의 비트01001100
carry in00000111
01101001
carry out00000110

부호 비트(8번째 자리) 계산을 보면 Cin=1C_{in} = 1(7번째 자리에서 넘어온 자리올림)이고, 8번째 자리 계산 결과 Cout=0C_{out} = 0(더 앞으로 넘어가는 자리올림 없음)이다. Cin(1)Cout(0)=1C_{in}(1) \oplus C_{out}(0) = 1이므로 오버플로가 발생했다.

결과 비트열은 부호 비트 1을 포함해 10010110210010110_2인데, 이는 8비트 2의 보수에서 음수로 해석된다. 실제로 100+50=150100 + 50 = 150은 8비트 2의 보수의 최댓값 127127을 넘으므로, 표현 범위를 벗어나 음수처럼 보이는 잘못된 결과가 나온 것이다. 이것이 오버플로의 실체다: 두 양수를 더했는데 결과가 음수로 나오는 모순이 규칙 1과 정확히 일치한다.

자주 틀리는 점

  • 자리올림(carry)과 오버플로(overflow)를 같은 것으로 착각하는 것이 가장 흔한 실수다. 부호 없는 수 연산에서는 최상위 자리에서 나가는 자리올림 자체가 “범위 초과”를 뜻하지만, 2의 보수(부호 있는 수) 연산에서는 그 자리올림은 그냥 버리고 CinCoutC_{in} \oplus C_{out}을 따로 확인해야 한다.
  • 서로 부호가 다른 두 수를 더하는 경우(즉 실질적으로 뺄셈인 경우)는 오버플로가 원천적으로 발생하지 않는다는 점을 놓치면 안 된다.

핵심 정리

  • 1의 보수는 전 비트 반전, 2의 보수는 1의 보수에 1을 더해 만들며, 2의 보수는 0이 하나뿐이라 현대 컴퓨터의 표준이다.
  • nn비트 2의 보수의 표현 범위는 2n12n11-2^{n-1} \sim 2^{n-1}-1로 비대칭이다(8비트: 128127-128 \sim 127).
  • 뺄셈은 감수를 2의 보수로 바꾼 뒤 덧셈으로 처리하며, 결과의 최상위 자리에서 넘치는 자리올림은 버린다.
  • 오버플로는 “두 피연산자의 부호가 같은데 결과 부호가 다르면 발생”, 형식적으로는 CinCout=1C_{in} \oplus C_{out} = 1일 때 발생한다.
  • 부호가 다른 두 수를 더하는 연산에서는 오버플로가 절대 발생하지 않는다.

마무리 복습

문제 14지선다
8비트 체계에서 십진수 -25를 2의 보수로 표현한 것은?
문제 24지선다
1의 보수 표현 방식의 단점으로 옳은 것은?
문제 34지선다
8비트 2의 보수 체계에서 표현 가능한 정수의 범위는?
문제 44지선다
8비트 2의 보수 연산에서 오버플로 발생 여부를 판정하는 형식적 규칙으로 옳은 것은?
문제 54지선다
8비트 2의 보수 체계에서 100 + 50을 계산했을 때 나타나는 현상으로 옳은 것은?
문제 64지선다
8비트 체계에서 52 - 19를 2의 보수 뺄셈으로 계산하는 과정에서 반드시 거치는 단계로 옳은 것은?

참고 자료

Last updated on