안타깝다. 사실 이전 연산자 위치에서 비트단위 연산은 할 일이 없을거라 판단하고 특별히 설명하지 않았다.
하지만 해야겠다는 생각이 들었다.
왜그러냐고?? 길게 말 못한다. 상도덕이라는게 있기 때문에
다만 오늘 1개월치 종강일이었고 종강시험을 쳤다고만 알아둬라.
그리고 이전 포스팅에서 막 포인터 들어갈거같이 긴장감 조성해놓고 갑자기 수준떨어지게 비트단위 연산을 다뤄서 미안하다. 나도 그러고싶지 않았다. 근데 이걸 더 미루면 더 이상해질 뿐일것같아서 늦었지만 지금이라도 쓴다.
비트 단위 연산자
일단 당연하게도 비트단위 연산은 컴퓨터만이 진행하지 인간은 그런걸 겪을 일이 없다. 아니 없었다 오늘 이전까지는
일전에 말했듯 컴퓨터는 모든 데이터를 비트 단위로 계산한다. 비트는 곧 2진수이며 그것을 연산하는것은 숫자를 연산하는것과 크게 다르지 않다. 다만 비트단위 연산을 할때만 하는 연산이 있기 때문에 그부분은 조금더 자세히 설명하겠다.
사칙연산
여러번 말하지만 이렇게 연산할 일 없다. 특히 사칙연산은 더더욱 없다. 다만 컴퓨터가 이런식으로 동작하는구나~~ 정도로 알고만 넘어가자
+연산
더하기다. 1과 1을 더하면 2가 된다. 그리고 그 2는 비트단위로 '10'이 된다.
9에서 1을 더하면 1의 자리수는 다시 0으로 가고 그 앞에있는 10의 자리수가 1이 되는것과 같은 원리다.
00000001
+ 00000001
==========
00000010
1의자리가 1+1이 되었으니 다시 0으로 가고 그 앞인 2의 자리수가 1이 된 것이다.
솔직히 이 이상 설명할 방법이 없다. 덧셈 하는법을 모르는 사람이 있다면 안타깝지만 나가주세요 여기 계시지 말고 초등학교를 가시는게 낫습니다.
그리고 만약 저 예시에서 왜 앞에 무의미한 0을 7개나 붙여놓았나요?? 라고 생각한다면 첫번째 포스팅을 보고와라.
보고 온 사람임에도 모르겠다면 컴퓨터의 최소 저장 단위는 1byte 이다. 1byte는 8비트이고 그래서 모든 데이터는 기본 8자리로 되어있다.
습관적으로 8자리를 맞추는것이 도움이 될 것이다. 라고 말하긴 힘들다 여러번 말했듯 이거 별로 할 일 없다.
뺄셈은 이하 동문이다. 하지만 그건 인간의 방식이고 컴퓨터의 방식을 설명해 주겠다. 이해안된다면 적당히 읽다가 넘어가라.
혹시 이진수로 변환을 하는 법을 모른다면
숫자를 2로 나누고 그 나머지를 적어라. 나머지가 0이어도 0을 적는거다.
그 나온 몫에 또 2를 나누고 나머지를 적어라. 당연히 앞에 나온 숫자의 앞에다 적어야한다.
그 몫이 1이 나올때까지 반복해라.
그렇게 나온 1까지 모두 적은게 그 숫자를 이진수한 값이다.
매번 그런 짓을 하는건 멍청한 짓이다. 하지만 우리에겐 컴파일러가 있으니 무서울건 없다.
cout << bitset<비트의 수>(원본 값) <<endl;
을 적어 출력해 봐라. 이진수로 나온다.
저기요 이거 안되는데요?? 할 수 있다.
맞다. 파일의 맨 위에
#include <bitset>
이라고 선언해라. 여기엔 세미콜론 (;)은 붙지 않는다.
뭔소리야?? 싶어도 그냥 해라 나중에 설명할꺼다. 무엇보다 나도 자세히 모른다.
10에서 3을 뺀다고 생각해보자 각각의 이진수 값은
10 = 00001010
3 = 00000011
이다. 덧셈과 같이 계산해도 무방하지만 컴퓨터는 다르다. 3이라는 숫자를 뒤집는다. 즉 0을 1로, 1을 0으로 만드는 것이다. 그러면 그 값은 11111100이 될 것이다. 거기다 1을 더한다. 왜냐고?? 컴퓨터는 0또한 양수로 취급하기 때문에 그대로 뒤집으면 -3-1이 되기 때문이다.
그래서 모든 비트를 뒤집은 뒤 1을 더한 값을 "보수"라고 부른다.
특정 수와 그 보수의 합은 0이 되는것이니 사람으로 따지면 앞에 부호만 바꾼것이다.
그래서 3의 보수는 11111101이다. 여기에 아까의 값 1010을 더한다.
덧셈 과정은 생략한다.
그러면 1/00000111이 된다.
야 그러면 맨앞에 1이 붙었으니까 값이 훨씬 커졌잖아??
맞다. 저 숫자는 단순히 십진수화 시키면 263이 된다. 하지만 컴퓨터는 저 8바이트를 벗어나는 값을 모른다. 정확하게는 할당 받은적 없다. 자기 권한 밖의 바이트인것이다.
그러니 컴퓨터가 읽어들이는 값은 앞의 1을 제외한 00000111 즉 7이 된다.
번거로운가?? 이상한가?? 맞다. 다만 컴퓨터에게 특정 값을 뺀다는 항목을 만드는것이 더욱 어려웠을것이다. 그래서 미세하게 덧셈보다 뺄셈이 느리다고 한다. 물론 체감할 일은 없다.
문제는 곱셈과 나눗셈이다.
우리가 생각하는 곱셈,나눗셈과는 조금 다른 의미가 될 것이다. 어떻게보면 또 전혀 다르지 않다.
우리가 일반적으로 두자릿수 이상의 곱셈을 할때를 상상해보자
예를들어 11과 12를 곱한다고 해보자.
우리는 11과 2를 곱한 값에 11과 10을 곱한 값을 더하는것이 자연스러운 곱셈이다.
그래서 나온 22와 110을 더하여 132라는 값이 나온다.
물론 그 이상의 두자릿수 곱셈을 하는 방법들이 있다지만 나는 수학에 두각을 드러내는 편이 아니므로 그 이상을 말할 수는 없다.
비트단위 곱셈 또한 마찬가지다.
예를들어 10과 3을 곱한다고 해보자
10 = 00001010
3 = 00000011
아까 보았던 각각의 비트 값이다. 우리는 어떻게 할까??
일반 곱셈과 같다. 1010과 1을 곱한 값에 1010과 10을 곱한 값을 더하면 된다. 즉 1010+10100이니 답은 자연히 11110이 된다. 그 값을 다시 우리가 사용하는 10진수로 변환하게 된다면?? 30이다.
이런 방식으로 숫자를 계산한 사람이 있다면 축하한다. 잘하셨다. 하지만 나라면 10*3한 값인 30을 2진수로 변환하는 과정을 거칠것이다.
그렇다면 컴퓨터가 이렇게 하니 설명한거겠지???
안타깝다 당신은 나를 너무 믿고있다. 컴퓨터는 이런 방식을 사용하지 않는다. 이런 방식을 사용하기 위해선 애초에 곱셈이라는 공식을 컴퓨터에 넣어야하지 않은가??
그럼 어떻게 하는데?!?!?
단순하다. for문 돌린다.
10*3이라면 10+10+10 한다는 것이다.
무식한가?? 근데 그래도 될정도로 똑똑한놈이 컴퓨터다.
나눗셈은 어떻게 할까??
곱셈처럼 일반적 10진수 나눗셈을 먼저 생각해보자. 아까했던 10을 3으로 이번엔 나누는 것이다.
수식을 적기엔 나는 여기 필기를 할 수가 없다. 알아서 머릿속에 나눗셈 수식을 적어보도록 하자.
____
3 | 10
글로 적다가 이게 직관적일거같다. 이러면 우리는 10에는 3이 3개 들어갈 수 있다 그럼 우리는 10 위에 3개를 적고 밑에 3*3인 9를 적게 될 것이다. 그러고 10-9를 해서 그 밑에 또 적을것이다.
3
____
3 | 10
9
1
물론 사람이라면 자연히 나머지 1뒤에 0을 붙여 10에다가 다시 3을 나누는 과정을 거치지만 컴퓨터는 여기서 멈춘다. 그러고 몫인 3을 반환하게 될 것이다. 정확하겐 몫3 나머지1 이다.
컴퓨터도 똑같다. 1010에 11이 몇개 들어가는가?? 3개 라고 말했다면 반은 맞고 반은 틀렸다. 이진수지 않은가. 11개다.
하지만 컴퓨터에겐 크나큰 장벽이 있다. 뺄셈이다. 아까 말했듯 컴퓨터는 빼는 연산을 매우 번거롭게 한다.
하지만 나눗셈은 필연적으로 뺄셈이 여러번 들어가게 되어있다. 그러다보니 사칙연산중 나눗셈이 가장 느린 작업이라고 한다.
자연스럽게 나머지 연산인 %또한 같은 원리로 진행되고 다만 최종 값이 마지막으로 나누어지지 못한 나머지를 반환할 뿐이다.
이상 사칙연산을 마친다.
근데 이게 비트연산이랑 무슨상관인데???
맞다 전혀 연관없다. 그냥 컴퓨터가 비트연산 한대서 비트방식으로 풀어줘 본 것이다.
이제 진정한 비트 연산자가 나온다
bit 주세요!!
~ 비트 반전 연산자
& 둘다 1이어야 1
| 둘다 0이어야 1
^ 둘이 달라야 1
<< 왼쪽 이동
>> 오른쪽 이동
이렇게 총 6가지 연산자들이 있다.
먼저 ~연산자는 숫자의 앞에 붙어 그 숫자를 뒤집는다. 아까 말했듯 0을 1로 , 1을 0으로 만든다.
이친구는 숫자 하나만 있을때 사용하고 나머지는 모두 숫자 두개 사이에 쓴다.
&연산자는 각 자릿수에 대응되는 값이 둘 다 1일 경우에만 1을 내보낸다.
1010 &1100이면
1000 이 되는 것이다.
하지만 십진수로 생각해보자 10과 12가 어떤 연산을 하면 8이 되는것인가??
그렇다 전혀 상관없다. 그냥 비트만을 본 결과값이다. 수학적으로 의미를 가지기 힘들다.
비트연산이란것이 원래 그런 놈들이니 수학은 빼고 생각하자.
| 연산자는 둘 다 0이어야만 1을 반환한다.똑같이 1010 | 1100이면 0001이 된다.
^는 둘이 다르면 1을 반환한다. 1010 ^ 1100 이면 0110이 된다.
여기서 의문을 가질 수 있다. 둘이 같은놈은 어떻게해??
그런 연산자는 없다. 하지만 ^연산한 결과를 ~로 뒤집으면 둘이 같은놈만 남을것이다.
<<는 모든 비트를 왼쪽으로 n칸 이동시킨다. 00001010<<2 하면 결과값은 00101000 이 되는 것이다.
의외로 이놈은 수학적 의미를 갖는다. 모르겠다면 00101000을 십진수로 변환해봐라 아마도 40이 될 것이다.
맞다 이것은 2의 n승한 값을 곱한값이 된다.
반대로 >>는 모든 비트를 오른쪽으로 n칸 옮기는 역할을 한다. 이또한 2의 n승으로 나눈 값과 동일하다.
만약 왼쪽 혹은 오른쪽으로 이동했는데 맨 앞 혹은 맨 뒤의 값이 바이트의 범위에서 벗어났다면 어떻게될까???
당연히 지워진다. 컴퓨터는 나간 값을 저장할 방도가 없다.
그래서 특정 값을 <<2 한 뒤에 >>2하면 당연히 같은 값을 뱉어낼것 같지만 꼭 그런것만은 아니다.
그리고 <<와 >>를 쉬프트 연산자라고 하는데 종종 이것들의 수학적 특성을 이용하여 프로그램에 사용되는 경우가 있다. 앞서 말했듯 2의 n승 곱셈 혹은 나눗셈의 역할을 하는데
특정 값 *2를 하는 것 보다 값<<1 하는것이 대략 열배 넘게 빠르다고 한다. 곱셈이 열배이상 차이가 나는데 매우 느리다고 했던 나눗셈의 경우 얼마나 차이가 날것인가?
때문에 속도가 중요한 게임 엔진이나 시스템 프로그래머들은 자주 사용한다고 한다. 물론 나는 아직 게임 프로그래머가 아니기 때문에 사용해 본 경험은 없다.
이상 비트단위 연산자를 마친다.
망할시험