1000 0000의 비트를 뒤집으면 0111 1111, 여기에 1을 더하면 다시 1000 0000입니다. ‘반전하고 1을 더하면 부호가 바뀐다’고 외웠는데 같은 비트열로 돌아왔어요.
비트를 뒤집고 더한 과정은 맞습니다. 8비트 2의 보수에서 원래 값은 −128이고, 반대 부호인 +128은 같은 폭에 담을 수 없어요. 표현 범위는 −128 이상 +127 이하입니다.1 정보처리나 컴퓨터일반의 정수 표현 문제에서도 비트열과 표현 범위를 함께 보아야 하는 이유예요.
아래 손셈은 결과의 아래쪽 8비트만 남기는 모델로 진행합니다. C 같은 특정 언어의 모든 실행 결과를 예측하는 예제는 아닙니다.
맨 앞 1을 떼면 절댓값이 될까요?
아니요. 2의 보수는 맨 앞 비트를 마이너스 기호처럼 떼고 나머지를 읽는 방식이 아닙니다. 최상위 비트는 음의 자릿값을 갖습니다.1
8비트 모델의 자릿값을 왼쪽부터 쓰면 다음과 같아요. 맨 앞자리만 −128이고 나머지는 일반 이진수와 같습니다.1
비트 자리 b7 b6 b5 b4 b3 b2 b1 b0
자릿값 -128 64 32 16 8 4 2 1
직접 만든 연습 비트열 1111 1010을 이 자리에 넣어봅시다.
-128 + 64 + 32 + 16 + 8 + 0 + 2 + 0 = -6
앞의 1을 떼고 나머지 111 1010에 마이너스를 붙이면 −122가 됩니다. 같은 비트열인데 다른 답이 나온 것은 부호·크기 표현의 읽는 법을 2의 보수에 적용했기 때문이에요. 이 글에서 정한 표현에서는 −6입니다.
이제 가장 작은 값을 만들려면 음의 자리만 켜고 양의 자리를 모두 끕니다. 1000 0000이므로 −128이에요. 가장 큰 값은 음의 자리를 끄고 나머지를 모두 켠 0111 1111이고, 64+32+16+8+4+2+1=127입니다.1
양수 쪽에는 +128이 없습니다. 영을 나타내는 0000 0000도 하나의 비트열을 차지하므로, 양수와 음수가 똑같은 개수로 짝을 이루는 표현이 아니에요.2 최솟값의 반대편에 자리가 없는 까닭이 여기 있습니다.
반전은 맞았고, 담을 자리가 없었습니다
비트를 모두 반전하고 1을 더하는 변환을 일반적인 값에 먼저 적용해볼게요.3 다음은 +6에서 −6을 만드는 자체 예제입니다.
시작 0000 0110 +6
모두 반전 1111 1001
1 더하기 1111 1010 -6
앞에서 자릿값으로 읽었던 −6과 같습니다. 반대로 1111 1010을 반전한 뒤 1을 더하면 0000 0110으로 돌아옵니다.
같은 손셈을 −128에 적용하면 도입의 결과가 나와요.
시작 1000 0000 -128
모두 반전 0111 1111
1 더하기 1000 0000 여전히 -128
수학적으로 원하는 값은 +128입니다. 하지만 그 수를 부호 없이 이진수로 쓴 1000 0000은, 이 글의 8비트 2의 보수 해석에서는 음의 자리가 켜진 수예요. 변환 과정과 반환 비트는 적었어도, 표현할 수 있는 양수로 부호가 바뀐 것은 아닙니다.
비트열만으로 부호 유무까지 결정되지는 않습니다. 1000 0000을 부호 없는 8비트 정수로 읽으면 128, 2의 보수로 읽으면 −128이에요.1 비트열 옆에 부호 유무와 폭이 붙어 있어야 숫자의 뜻이 정해집니다.
자리올림과 오버플로는 같은 표시가 아닙니다
다음 자체 계산은 최상위 자리 밖으로 올라간 비트가 없어도 부호 있는 오버플로가 생기는 경우입니다. 오버플로는 정확한 산술 결과가 정한 표현 범위를 벗어나는 것을 말해요.2
0111 1111 127
+ 0000 0001 1
-----------
1000 0000 -128로 읽힘
정확한 합은 128인데 모델이 남긴 비트열은 −128로 읽힙니다. 양수끼리 더했는데 음수가 되었고, 앞에서 구한 최댓값도 넘었어요. 맨 왼쪽 밖으로 자리올림이 없었다는 사실로 정상 덧셈이라고 판정할 수 없습니다.
이번에는 반대로 자리올림이 있지만 합은 정상입니다.
0000 0110 6
+ 1111 1010 -6
-----------
1 0000 0000
└ 아래 8비트는 0000 0000, 즉 0
정확한 합인 0을 남긴 비트열로 표현할 수 있습니다. 밖으로 나간 1을 버렸다고 해서 부호 있는 합이 틀어진 것은 아니에요. 같은 가산 결과라도 부호 없는 수로 읽을 때와 2의 보수로 읽을 때 범위 초과의 판단은 달라집니다.2
종이 위의 이 모델을 그대로 C 코드의 규칙으로 옮기지는 않습니다. C의 부호 있는 정수 산술에서 표현 범위를 넘는 연산은 정의되지 않은 동작이 될 수 있어요.4 코드 문제에서는 저장 변수의 폭뿐 아니라 정수 승격을 거쳐 연산이 실제 일어나는 자료형도 보아야 합니다. 여기서는 범위를 넘긴 정답과 남은 비트를 구별하는 데까지만 계산했습니다.
처음의 −128에 대응하는 양수를 표현하려면 더 넓은 폭을 쓰면 됩니다. 음수의 값을 유지하는 부호 확장은 왼쪽에 기존 부호 비트를 복제해 붙입니다.5 자체 예제로 16비트까지 늘리면 다음과 같아요.
8비트의 -128 1000 0000
16비트로 확장 1111 1111 1000 0000
반전 0000 0000 0111 1111
1 더하기 0000 0000 1000 0000 +128
확장한 수는 −32768+16384+8192+4096+2048+1024+512+256+128=−128입니다. 앞에 1을 붙였어도 값은 그대로이고, 넓어진 자리에서 부호를 바꾸니 +128을 표현할 수 있어요. 8비트의 1000 0000과 16비트의 0000 0000 1000 0000은 끝의 모양이 같지만 최상위 자리의 위치가 다릅니다. 처음 계산에서 빠져 있던 것은 반전 방법이 아니라 양수를 담을 자리였습니다.
Footnotes
-
Cornell CS 3410 「Switches and Numbers」의 Signed Numbers → Two’s Complement에서 최상위 자리의 가중치 −2^(n−1), 최댓값 2^(n−1)−1, 최솟값 −2^(n−1)을 확인했다. 본문의 8비트 값과 비트열 계산은 이 정의를 적용한 자체 예제다. 정수 표현의 자릿값 ↩ ↩2 ↩3 ↩4 ↩5
-
UC Berkeley CS 61C 「Integer Representations」의 4.3 Integer Overflow에서 표현 범위 초과 정의, 7장의 영·양수·음수 개수, 7.1에서 최상위 자리올림을 버리는 덧셈을 확인했다. 127+1과 6+(−6)의 대조는 직접 계산했다. 범위와 덧셈 ↩ ↩2 ↩3
-
Cornell의 Thomas Finley 「Two’s Complement」에서 모든 비트 반전 뒤 1을 더하는 변환을 확인했다. 본문의 6과 −128은 원문 예제를 옮기지 않고 직접 구성했다. 반전과 1 더하기 ↩
-
Cornell CS 3410 「Introduction & C」의 Overflow에서 C의 부호 있는 정수 산술이 범위를 초과할 때 undefined behavior가 되는 설명을 확인했다. 폭을 제한한 비트 모델을 언어 실행의 보장으로 쓰지 않았다. C 연산과 범위 초과 ↩
-
Cornell CS 3410 「Load & Store」의 Extension and Truncation → Extension에서 zero extension과 sign extension을 구별하고, 부호 확장은 기존 최상위 비트를 상위 자리에 복제하는 설명을 확인했다. 8→16비트 수치는 자체 계산이다. 부호 확장 ↩