빼다. 어쩌면 인간에게 이 만큼 직관적인 연산이 존재할까 의문이 든다. 내가 가지고 있는 것이 다양한 이유로 이탈한다는 것은 생존과 직결된 문제를 야기할 수 있다. 많은 경우에 인간은 대상의 품질과 크기에도 불구하고 갯수를 중심으로 판단하고 사고한다. 때문에 복잡한 셈을 연산하는 힌트는 교육과정에서 주어질 지 언정, 뺄셈에 대한 기초 자체는 사탕이나 초콜렛을 예시로 들면 아이들이 어렵지 않게 따라온다.
하지만 이 친구는 다르다. 차원을 뛰어넘는 친구다. 이 친구에게서 사탕과 초콜렛을 빼앗아봤자, 일말의 아쉬움도 번뜩이는 직관도 느낄 수 없다. 그래서 우리는 대상에서 원하는 만큼을 없앤다는 개념을 새로이 가르칠 필요가 있다.
그렇다면 뺄셈을 어떻게 표현할 수 있을까. 일단 중학교 무렵쯤 배웠던 부호의 개념을 다시 떠올릴 필요가 있다. 뺄셈은 한 수에서 다른 한 수의 부호를 바꾸어 더하는 것과 마찬가지이고, 우리는 덧셈을 구현할 수 있기 때문이다.
n - m = n + (-m)
우리가 뺄셈을 구현하기 위해서는 어떻게 음수를 구현할 것인가만 찾으면 된다.
우리는 익히 컴퓨터가 정보를 저장하는 방식이 비트에 저장된 기호와 순서, 그리고 해석이라는 것을 알고 있다. 때문에 우리가 10년 뒤의 우리에게 패스워드로 -8을 알려주라고 한다면 8을 나타내는 비트묶음 1000에다가 얘를 +가 아닌 -라는 것을 상기시켜줄 비트 하나만 추가해주면 된다. 그리고 0을 양수로 취급한다면 1을 저장하고, 1을 양수로 취급한다면 0을 저장하기만 하면 된다. 2진수 자체로 자연스러운 형태이니 1000이 특이한 경우를 1로 표기한다면 아래와 같은 형태가 된다.
-8 = bit(1 1000)
매우 직관적이다. 이러한 아이디어라면 모든 음수를 비트로 변환하는데 큰 어려움을 겪지 않을 것이다. 이진수 변환 방식에 부호를 표현하는 비트 하나만 추가하면 그만이기 때문이다. 하지만 우리가 원래 음수 표현을 탐구하려 했던 목적인 뺄셈 계산에는 적합할까?
0 1000 + 1 1000 = 0 0000
기본적인 논리연산인 AND, OR, XOR로 해당 연산을 구현하는 것은 매우 복잡하다. 자릿수끼리 비교하면 1과 1을 연산했는데 0이 나오면서 0과 0을 연산하여 0이 나와야하니 XOR로 충족할 수 있다.
0 0111 + 1 0101 = 0 0010
문제는 두 가지가 다를 때의 연산이다. 1과 0을 연산하여 1이 나와야하므로 OR연산이 포함되어야 한다.
0 0011 + 1 0100 = 1 0001
한 눈에 봐도, 부호의 형태를 인지해서 덧셈 연산의 규칙을 변경해야하는 것을 알 수 있다. 더 나아가 위의 표현 방식은 0 0000과 1 0000으로 0을 표현하는 방식이 두 가지인 점도 단점이다.
뺄셈을 표현한다는 원래의 계획으로 돌아가보면, 일단 어떤 수와 부호만 다른 음수는 덧셈하여 0이 되는 수여야 한다는 것을 알 수 있다. 그렇다면 AND와 XOR로 어떻게 하면 0을 표현할 수 있을까.
1의 보수 표현법은 절대값이 같은 양수와 음수를 더했을 때, 모든 자릿수가 1로 나오는 수를 음수로 표기하는 방법이다. 어떤 수의 모든 자릿수를 NOT연산을 통해 반환한 비트묶음을 해당 수의 음수로 설정하는 것이다. 0 111은 7을 가리키므로, 1 000이 -7을 나타내는 식이다.
-1 => bit(1 110)
-2 => bit(1 101)
-3 => bit(1 100)
-4 => bit(1 011)
-5 => bit(1 010)
기존 덧셈 연산으로 해당 뺄셈 연산을 해보자.
7 + (-7) => bit(0 111) + bit(1 000) = bit(1 111)
6 + (-6) => bit(0 110) + bit(1 001) = bit(1 111)
5 + (-5) => bit(0 101) + bit(1 010) = bit(1 111)
절대값이 같은 양수와 음수를 더했을 때, 해당 방법은 연산 결과가 1 111의 형태가 나온다. 그렇다면 1 111의 정체는 무엇일까? 바로 0 000을 뒤집은 -0 즉 같은 0이다. 그렇다면 절대값이 다른 음수와의 연산은 어떨까
2 + (-1) > (0 010) + (1 110) =
XOR(1 100)+AND(0 100) =
XOR(1 000)+AND(1 000) =
XOR(0 000)+AND(10 000)
1의 보수 표현법으로 진행하는 덧셈 연산에서는 위와 같이 비트 수를 벗어나는(오버플로) 뺄셈이 진행되는 경우가 있다. 해당 문제를 보완하기 위해서 비트를 벗어난 올림수를 가장 작은 자릿수의 비트에 추가하는 규칙을 추가해야, 올바른 값을 반환한다. 해당 보완규칙을 순환 올림이라고도 한다. 아무튼 해당 규칙이 시사하는 바는, 덧셈 연산(뺄셈 연산)이 단순하지 않다는 것이다.
위의 덧셈 법칙의 부차적인 규칙을 보완하기 위한 방법이 2의 보수 표현법이다. 이 방법은 절대값이 같은 양수와 음수를 더했을 때, 모든 자릿수가 2(혹은 0)으로 반환되는 비트를 음수로 표현하는 방식이다. -7의 경우 0 111과 더해서 1 (혹은 0) 0 000이 되는 1 001으로 표기하는 방법이다. 해당 방법에서는 비트를 넘어서는 값을 버리는 방식을 통해 0 000의 값을 반환한다. 더군다나 0 000과 더해서 0 000이 되는 0 000이 -0 즉 0이 되는 표현방법이다. 0의 표현법이 한 가지라는 의미이다.
그렇다면 절대값이 서로 다른 두 수를 더할 때도 마찬가지로 효율적일까?
2 + (-1) > (0 010) + (1 111) =
XOR(1 101) + AND(0 100) =
XOR(1 001) + AND(1 000) =
XOR(0 001) + AND(1 0 000)
1의 보수 표현법에서의 뺄셈과 큰 차이를 보이진 않는다. 다만, 순환올림을 진행할 필요가 없으며, 저장 비트를 넘어간 수를 신경쓸 필요가 없다. 1의 보수표현법의 순환올림 문제를 아주 직관적으로 해결했다고 할 수 있다. 때문에, 2의 보수로 표현한 음수를 구하는 방법은 양의 정수를 표현한 비트를 NOT연산으로 반환한 값에 1을 더한 형태이다.
해당 표현법은 2진법으로 양수를 표현한 경우처럼 우리가 의도한 범위의 모든 양의 정수, 음의 정수, 0을 효과적으로 표기할 수 있다. 참고로 비트의 개수가 4개일 때는 7 ~ -8까지를 표현할 수 있고, 8개일 때는 127 ~ -128까지를 표현할 수 있다.
표현 범위 : n개의 비트로 (2n-1-1) ~ (-2n-1)까지 2n개의 수를 표현할 수 있다.
뺄셈이 제일 어려웠어요