모든 이야기

2의 보수: 같은 덧셈 회로로 음수까지 계산하는 표현

고정된 n비트로 양수와 음수를 함께 표현할 때 널리 쓰는 방법이 2의 보수다. 최상위 비트가 양수의 크기에 더해지는 대신 음의 가중치를 가진다고 해석하면 값을 계산할 수 있다. n비트의 범위는 -2^(n-1)부터 2^(n-1)-1까지다. 음수가 하나 더 많아 범위가 비대칭이며, 0은 한 가지 패턴으로 표현된다.

8비트 5의 비트를 뒤집고 1을 더해 -5인 11111011을 얻는 과정과 -128부터 127까지의 범위

고정 비트 폭과 부호 해석을 함께 정해야 2의 보수 패턴의 값을 읽을 수 있다.

-5를 8비트로 만드는 과정

8비트의 5는 00000101이다. 모든 비트를 뒤집으면 11111010, 여기에 1을 더하면 11111011이 된다. 이 패턴을 2의 보수로 읽으면 -5다. 뒤집고 1을 더하는 방법은 고정 폭에서 덧셈의 역원을 얻는 절차다. 단순히 맨 앞에 부호 비트를 붙이는 부호와 크기 표현과는 다르다.

가중치로 확인하면 최상위 비트는 -128이고 나머지는 64+32+16+8+2+1이므로 합계 -5가 된다. 같은 패턴을 부호 없는 8비트 정수로 해석하면 251이다. 비트 자체가 부호를 지닌다기보다 타입과 연산 규칙이 패턴의 의미를 정한다. Java의 정수 타입 규정은 byte·short·int·long을 지정한 폭의 부호 있는 2의 보수 값으로 정의한다.

덧셈을 같은 비트 연산으로 한다

8비트에서 5와 -5의 패턴을 더하면 아홉 번째 자리로 넘어가는 올림이 생기고, 아래 여덟 비트는 모두 0이 된다. 고정 폭 비트 덧셈을 모듈러 산술로 보는 관점이다. 덧셈 회로의 구조를 음수 전용으로 새로 만들지 않고도 부호 정수 계산에 사용할 수 있다. 그러나 산술 결과가 표현 범위 안인지 확인하는 오버플로 판단은 별도의 문제다.

예를 들어 8비트 부호 정수 모델에서 127에 1을 더한 아래 비트는 10000000이 되어 -128로 해석된다. 실제 수학적 합 128은 범위 밖이다. 부호 없는 올림과 부호 있는 오버플로는 같은 조건이 아니다. 같은 부호의 두 수를 더했는데 결과 부호가 바뀌는 경우 등이 부호 오버플로 판단의 출발점이 된다.

가장 작은 음수의 특이점

8비트 범위는 -128부터 127이다. -128의 양수 대응인 128은 같은 부호 타입으로 표현할 수 없다. 그래서 가장 작은 음수에 단순히 음수를 취하는 연산이 범위 문제를 만들 수 있다. 절댓값 함수나 부호 변환에서 이 경계 조건을 놓치면 일반적인 작은 입력에서는 보이지 않는 오류가 생긴다.

폭을 늘릴 때는 부호 확장을 사용한다. -5의 8비트 패턴 앞에 최상위 1을 반복해 16비트 1111111111111011로 만들면 값이 유지된다. 양수는 앞에 0을 붙인다. 부호 없는 값의 0 확장과 구분해야 하며, 좁은 타입으로 줄일 때는 일부 비트가 잘리므로 원래 값이 항상 유지되지 않는다.

언어마다 범위 밖 연산의 의미가 다르다. 일부 언어의 고정 폭 정수는 정해진 래핑을 제공하지만 C나 C++의 부호 있는 오버플로는 단순 래핑으로 가정할 수 없다. Python의 일반 정수처럼 임의 정밀도를 제공하는 경우도 있다. CPU의 비트 표현과 언어의 연산 계약을 함께 확인해야 한다.

2의 보수는 음수 표현, 부호 확장, 비트 덧셈을 하나의 규칙으로 연결한다. 메모리 덤프에서 큰 부호 없는 숫자가 작은 음수와 같은 패턴인 이유도 설명한다. 계산할 때는 비트 폭, 부호 해석, 범위 밖 동작을 항상 같이 적는 습관이 중요하다.

참고 자료

2의 보수: 같은 덧셈 회로로 음수까지 계산하는 표현 · ariser