TechNotes
CPU에 감산기가 없는 이유
반가산기와 전가산기, Carry In, 2의 보수를 통해 CPU가 감산기 없이 뺄셈을 수행하는 원리
컴퓨터는 수많은 덧셈과 뺄셈을 수행한다.
그렇다면 CPU 내부에는 덧셈을 위한 가산기(Adder) 와 뺄셈을 위한 감산기(Subtractor) 가 각각 존재할까?
현대 CPU는 대부분 별도의 감산기를 두지 않는다.
대신 가산기 하나만으로 덧셈과 뺄셈을 모두 수행한다.
이 구조를 이해하려면 먼저 반가산기와 전가산기부터 살펴볼 필요가 있다.
반가산기와 전가산기
반가산기(Half Adder)
반가산기는 가장 단순한 덧셈 회로이다.
두 개의 1비트를 더해 합(Sum) 과 자리올림(Carry) 을 계산한다.
| A | B | Sum | Carry |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 0 | 1 | 1 | 0 |
| 1 | 0 | 1 | 0 |
| 1 | 1 | 0 | 1 |
하지만 반가산기는 이전 자리에서 발생한 Carry를 입력받을 수 없다.
즉, 여러 비트의 덧셈에는 사용할 수 없다.
전가산기(Full Adder)
전가산기는 이전 자리에서 넘어온 Carry까지 계산할 수 있다.
입력은 다음과 같다.
A
B
Carry In
출력은
Sum
Carry Out
이다.
Carry Out은 다음 비트의 Carry In으로 전달된다.
Carry → Carry → Carry
┌────────┐ ┌────────┐ ┌────────┐
│ Full │──▶│ Full │──▶│ Full │
│ Adder │ │ Adder │ │ Adder │
└────────┘ └────────┘ └────────┘
이처럼 전가산기를 여러 개 연결하면 CPU는 8비트, 32비트, 64비트 정수까지 모두 덧셈할 수 있다.
여기까지는 모두 덧셈 회로이다.
그렇다면 뺄셈은 어떻게 수행할까?
감산기를 따로 만들면 되지 않을까?
가장 단순한 방법은 감산기를 하나 더 만드는 것이다.
하지만 그렇게 하면
- 회로가 복잡해지고
- 트랜지스터 개수가 증가하며
- 칩 면적이 커지고
- 전력 소모도 증가한다.
CPU 설계자는 이미 존재하는 가산기를 그대로 활용하는 방법을 선택했다.
그 결과 등장한 것이 2의 보수(Two’s Complement) 이다.
컴퓨터는 음수를 어떻게 표현하는가
컴퓨터는 메모리에 0과 1만 저장할 수 있다.
따라서 음수를 별도의 기호로 저장하는 것이 아니라 2의 보수를 이용해 표현한다.
2의 보수는 다음과 같이 계산한다.
- 양수를 2진수로 표현한다.
- 모든 비트를 반전하여 1의 보수를 만든다.
- 1을 더한다.
즉,
2의 보수 = 1의 보수 + 1
이다.
예를 들어 -17을 8비트로 표현하면 다음과 같다.
+17
0001 0001
↓
1의 보수
1110 1110
↓
+1
1110 1111
따라서
1110 1111 = -17
이 된다.
2의 보수는 왜 만들어졌을까?
컴퓨터는 음수를 표현하기 위해 2의 보수를 사용한다.
라고만 알고 있었었고, 왜 하필 2의 보수인지는 설명할 수 없었다.
2의 보수는 CPU가 감산기 없이 뺄셈을 수행하기 위해 선택한 방식이다.
전가산기의 기본 연산은
A + B + Carry In
이다.
CPU는 하나의 제어 신호를 이용해 덧셈과 뺄셈을 구분한다.
덧셈
Carry In = 0
A + B
뺄셈
CPU는 B의 모든 비트를 반전시키고 Carry In을 1로 설정한다.
Carry In = 1
A + (~B)
그러면 실제 계산은
A + (~B) + 1
이 된다.
그런데 이 식은
2의 보수 = 1의 보수 + 1
과 정확히 동일하다.
즉,
CPU는
- B를 반전시키고
- Carry In에 1을 넣어
하드웨어에서 자동으로 2의 보수를 만든다.
결국
A - B
는
A + (-B)
로 변환되어
가산기 하나만으로 계산된다.
이것이 CPU에 감산기가 없는 이유이다.
실제로 5 - 5를 계산해 보자
먼저 +5를 표현한다.
0000 0101
-5의 2의 보수는
0000 0101
↓
1111 1010
↓
1111 1011
이다.
이를 더하면
0000 0101
1111 1011
-----------
1 0000 0000
8비트에서는 가장 앞의 Carry를 버린다.
따라서
0000 0000
이 남는다.
즉,
5 + (-5) = 0
이 정상적으로 계산된다.
CPU 입장에서는 뺄셈을 수행한 것이 아니라 음수를 더한 것이다.
-1이면서 동시에 가장 큰 수인 이유
다음 비트를 보면
11111111
이 비트를 signed로 해석하면
-1
이다.
왜냐하면
00000001
의 2의 보수가
11111111
이기 때문이다.
반면 unsigned에서는 부호 비트가 존재하지 않는다.
따라서
11111111
은
255
가 된다.
| 비트 | signed | unsigned |
|---|---|---|
| 11111111 | -1 | 255 |
즉,
같은 비트라도 해석 방식에 따라 의미가 달라진다.
강의에서
“11111111은 -1이면서 가장 큰 수도 된다.”
라고 설명한 이유가 이것이다.
왜 -1을 많이 사용할까?
프로그래밍에서는 -1을 특수한 의미로 자주 사용한다.
대표적으로
- 검색 실패
- 존재하지 않는 값
- 초기화되지 않은 인덱스
- 종료 신호
- 오류 코드
등을 표현할 때 사용된다.
예를 들어
int index = -1;
은 일반적으로
아직 유효한 인덱스가 없다.
는 의미로 사용된다.
또한
size_t index = -1;
처럼 unsigned 타입에 저장하면 내부적으로는 모든 비트가 1이 되며 해당 자료형의 최댓값으로 해석된다.
정리
- 반가산기는 두 개의 1비트를 더한다.
- 전가산기는 Carry In을 이용해 여러 비트의 덧셈을 수행한다.
- CPU는 별도의 감산기를 두지 않는다.
- 뺄셈은 B를 반전시키고 Carry In을 1로 설정하여 수행한다.
- 이는 2의 보수를 만드는 과정과 동일하다.
- 결국 CPU는 가산기 하나만으로 덧셈과 뺄셈을 모두 처리한다.
- 같은 비트라도 signed와 unsigned의 해석 방식에 따라 전혀 다른 값이 될 수 있으며,
11111111은 signed에서는-1, unsigned에서는 최댓값이 된다.
댓글