2의 보수로 음수를 표현하는 이유
부호 있는 정수의 비트 표현과 오버플로 이해하기
본 블로그 작성 글들은, 강의를 목표로 하고 제작되었습니다. 또한 작성된 글의 정보도 틀렸을 수 있으니, 주의해주세요.
컴퓨터는 양수와 음수를 모두 비트로 저장한다. 현대 컴퓨터의 부호 있는 정수는 대부분 2의 보수 방식으로 표현하며, 덧셈 회로를 양수와 음수에 함께 사용할 수 있다는 장점이 있다.
가장 높은 비트가 음수의 가중치를 가진다
4비트 2의 보수 정수를 예로 들면 각 비트의 가중치는 다음과 같다.
비트 위치: 3 2 1 0
가중치: -8 4 2 1
따라서 0101은 5이고 1101은 다음처럼 -3이 된다.
1101₂ = -8 + 4 + 0 + 1 = -3
n비트 2의 보수 정수의 범위는 -2^(n-1)부터 2^(n-1)-1까지다. 4비트라면 -8부터 7까지이며 음수 쪽이 하나 더 많다.
음수 비트 패턴 만들기
양수의 모든 비트를 뒤집고 1을 더하면 대응하는 음수의 비트 패턴을 얻을 수 있다.
3 = 0011
비트 반전 = 1100
1 더하기 = 1101 = -3
3 + (-3)을 같은 4비트 덧셈으로 계산하면 다음과 같다.
0011
+ 1101
------
1 0000
4비트 밖의 올림을 버리면 결과는 0이다. 별도의 뺄셈 표현을 만들지 않고 같은 이진 덧셈 회로로 음수 연산을 처리할 수 있다.
부호 확장
작은 부호 있는 정수를 더 큰 비트 수로 바꿀 때는 가장 높은 부호 비트를 왼쪽에 반복한다.
4비트 -3 : 1101
8비트 -3 : 11111101
앞에 0을 채우면 00001101, 즉 13이 되어 값이 바뀐다. 반대로 부호 없는 정수를 확장할 때는 왼쪽을 0으로 채운다.
오버플로는 비트 해석의 문제다
4비트에서 가장 큰 값 7에 1을 더해보자.
0111 + 0001 = 1000
같은 비트 패턴 1000은 부호 있는 4비트 정수에서 -8이다. 두 양수를 더했는데 음수가 되었으므로 표현 범위를 벗어난 부호 있는 오버플로가 발생했다.
하드웨어 비트 결과가 같아도 프로그래밍 언어의 규칙은 다를 수 있다. C와 C++의 부호 있는 정수 오버플로는 정의되지 않은 동작이고, Java의 고정 폭 정수는 2의 보수 범위로 감싸며, Python의 일반 정수는 필요에 따라 더 큰 크기를 사용한다.
#include <cstdint>
#include <limits>
std::int32_t value = std::numeric_limits<std::int32_t>::max();
// value + 1을 계산하면 C++에서는 정의되지 않은 동작이다.
2의 보수 비트 패턴을 이해하는 것과 언어가 해당 연산을 허용한다는 것은 다른 문제다. 네트워크, 파일, 암호화처럼 정확한 비트 폭이 필요한 코드는 고정 폭 타입과 부호 규칙, 오버플로 정책을 명시해야 한다.