[ 숫자 / 과학 / 수학 ] " 2147483647 " 숫자에 얽힌 비밀.TXT
이번 글에서 알아볼 것은 오버플로우(overflow)이다.
2147483647에 대해 알아보자.
혹시 게이들은 이 숫자를 이전에 접한 적이 있노?
관심있게 보지 않은 게이들은 잘 모르겠지만 아마 살면서 저런 비슷한 숫자를 한번쯤은 보지 않았을까 한다.
예를 들면, 나는 어떤 프로그램을 돌리는데 -2147483648번 에러를 본 적이 있다.
사실 왜 그 에러가 -214783648번인지는 모른다. 짐작은 간다만..
혹은 각종 게임들에서 127혹은 255,
32767혹은 65535,
2147483647 혹은 4294967295 와 같은 수들을 본 적이 있을 것이다.
아무튼 이 숫자가 무엇이길래 이런 정보글까지 싸는 것일까?
0, 1번은 오늘 설명할 오버플로우에 대한 배경지식이다.
2진수로 숫자를 저장하는 것에 대해 궁금한 게이들은
컴퓨터의 덧셈 - http://www.ilbe.com/4726652237
컴퓨터의 뺄셈 - http://www.ilbe.com/4732934969
를 참고해라.
특히 음수를 표현하는 것에 관해서는 뺄셈 글에 상세하게 설명되어 있다.
0. 정수형
컴퓨터는 모든 데이터들을 2진수로 저장한다.
그래서 정수 또한 2진수로 저장한다.
2진수 : 0(Off)와 1(On)만으로 표현
2자리의 2진수로 최대 4가지의 숫자를 표현할 수 있다.
00 01 10 11
이 숫자들은 각각 0,1,2,3에 해당한다.
n자리의 2진수 (n비트라고 칭한다.)로 최대 2^n가지의 숫자들을 표현할 수 있다.
현재의 1바이트는 8비트를 의미하고, 1바이트는 2^8개의 숫자를 표현할 수 있다.
256가지의 숫자.
만약 이 숫자들이 전부 0 이상의 수라면 0부터 255까지 표현할 수 있을 것이다.
하지만 숫자에는 양수만 있는 것이 아니라 음수도 존재한다.
음수 / 0이상 으로 나누어서 반반의 수들을 표현할 수 있다.
-128~127
1바이트로는 총 256가지, -128~127의 수를 표현할 수 있다.
정수형은 2의 거듭제곱의 크기를 가진다.
1바이트, 2바이트, 4바이트, 8바이트, ...
현재 32비트 컴퓨터에서는 메모리 주소가 32비트이기 때문에 하나의 정수형으로 이를 표현할 수 있도록 32비트(4바이트)의 정수형을 기본적으로 제공하고
64비트 컴퓨터에서는 메모리 주소가 64비트이기 때문에 64비트(8바이트) 정수형도 제공한다.
부호가 있는 수를 기준으로 각 정수형들은 다음과 같은 표현 범위를 가진다.
1바이트 : -128~127
2바이트 : -32768~32767
4바이트 : -2147483648~2147483647
8바이트 : -9223372036854775808~9223372036854775807
2147483647과 마찬가지로 32767에도 익숙한 게이들이 있을 것이다.
부호가 없는 정수형도 있다.
이들은 원래 부호가 있는 정수형에서 음수로 할당했던 표현들에 대해 양수로 할당을 한 것이다.
1바이트 : 0~255
2바이트 : 0~65535
4바이트 : 0~4294967295
8바이트 : 0~18446744073709551615
이 수들은 정수형의 최대값이기 때문에 각종 게임에서 메모리를 아끼고자 최적화를 한 개발자들 덕분에 능력치 등의 최대로 (당연하게도) 쓰이게 된 것이다.
1. 음수
양의 정수를 표현한 부분은 일반적으로 수학시간에 배우는 진법표현과 같기 때문에 별 문제가 되지 않을 것이다.
00 = 2*0+1*0 = 0
01 = 2*0+1*1 = 1
10 = 2*1+1*0 = 2
11 = 2*1+1*1 = 3
하지만 음수는 어떨까?
음수는 다소 와닿지 않을 수 있다.
컴퓨터가 덧셈과 뺄셈을 하는 방식을 안다면 음수의 표현법이 이해갈 것이다.
음수는 다음과 같이 표현된다
-n : "n의 2의 보수"
기본적으로 보수(complement)란, 모든 비트를 반전 시켜주는 것이다.
원래 0인 놈은 1로, 1인 놈은 0으로.
그리고 2의 보수는 여기에다가 1을 더해주는 것이다.
부호가 있는 3비트로 예를 들어보자.
부호가 있는 3비트는 -4~3까지 표현이 가능할 것이다.
00 : 0
01 : 1
10 : 2
11 : 3
이제 -4, -3, -2, -1에 대해 고민해보자.
먼저 4는 100이다.
다만 이것은 비트의 크기가 정의되지 않았을 때의 이야기고, 우리가 살펴볼 부호가 있는 3비트 정수에서는 100은 다른 의미를 가진다.
각설하고, -4를 계산하도록 해보자.
-4 : 4의 2의보수
100을 보수 취한것 : 011
011+1 = 100
그렇다.
여기에서 100은 4가 아닌 -4를 의미하는 것이다.
다음은 3으로 해보자.
3 = 011
~3 = 100 (~ : 보수를 의미)
~3+1 = 101
그렇다. 101은 -3를 의미한다.
다른 수들도 해보면 -4~3에 대해 다음과 같은 관계를 얻을 수 있다.
다음은 비트를 오름차순으로 나타낸 것이다.
0 : 000
1 : 001
2 : 010
3 : 011
-4 : 100
-3 : 101
-2 : 110
-1 : 111
왜 이렇게 되는 지를 알고 싶다면, http://www.ilbe.com/4732934969 이 글을 참고하면 더욱 자세하게 알 수 있다.
2. 오버플로우
그럼 이제 2147483647이 왜 중요한 의미를 가지는 지, 오버플로우란 무엇인지에 대해 알아보자.
부호가 있는 4바이트(32비트) 정수형에서
가장 큰 정수는 2147483647일 것이다. (2^31-1)
그리고 이는 2진수로
0111111111111.... (0이 1개, 1이 31개) 로 표현될 것이다.
여기서 +1을 해주면 어떻게 될까?
2147483648이 될까?
앞에서 봤듯이 첫번째 비트가 1인 수들은 모두 음수임을 알 수 있었다.
여기서도 마찬가지.
100000000000... (1이 0개, 0이 31개)는 음수를 의미한다
가장 작은 음수. -2147483648을 의미한다.
이처럼 양수를 더해서 음수가 나오거나 음수끼리 더해서 양수가 나오는 등의 경우를 오버플로우라고 부른다.
(다르게 말하자면, 계산 결과가 저장할 수 있는 범위보다 큰 경우를 의미한다)
위에서 언급한 부호가 있는 3비트상에서 오버플로우의 예시이다.
3 + 1 = 011 + 001 = 100 = -4
-3 + -3 = 101 + 101 = 1010 = 010 = 2
(두번째 예제에서, 계산결과는 1010이나 첫번째 비트인 1을 저장할 수가 없기 때문에 010이 되는 것은 오버플로우(사전적 의미로 "넘쳐흐른다")의미를 잘 보여준다.)
3. 오버플로우가 무 슨 문제?
지금까지 오버플로우가 무엇인지 설명했는데, 과연 오버플로우가 왜 문제일까?
눈치 빠른 게이들은 알 수 있겠지만, 실제의 값을 바꿔버리는 문제가 발생한다.
위에서 3+1은 -4가 되었다.
이것은 엄청난 문제이다.
실제 값은 4이나 -4로 인식한다는 것..
누군가는 다음과 같이 질문할 수 있다. :
"100은 2진수로 4를 의미하는데, 그렇다면 부호 3비트 하가 아닌 부호 4비트나 부호가 없는 3비트로 해석한다면 문제가 없는데, 무 슨 문제가 있느냐?"
먼저 부호 4비트를 경우와 같이 저장 범위가 부족할 때 마다 1비트씩 늘려가는 전략을 쓴다면, 엄청난 혼란이 올 것이다. 이것은 단순히 이 수의 연산에만 국한되는 문제가 아니라 다른 메모리에도 영향을 줄 수 있기 때문이다. (메모리들의 위치를 전체적으로 옮겨야 한다는 등의 문제가 발생할 수 있다.)
두번째의 부호가 없는 3비트로 해석한다는 대안은, 음수에 대한 정보가 모두 날아간다는 점에서 명시적인 문제가 보인다.
(물론, 음수가 절대 나올 일이 없는 시스템에서는 부호가 없는 정수 체계를 쓸 수도 있다. 하지만 이 경우에 뺄셈 연산이 있는 경우, 0에서 1을 뺄 때 가장 큰 수가 되어버리는 문제가 발생할 수도 있을 것이다.)
4. 오버플로우의 예시
양 세기
위 그림은 2바이트에서 오버플로우가 발생한 예이다.
잠이 안오는 일게이는 양을 한마리씩 세다가, 32767마리까지 센 상태에서 한 마리를 더 세는 순간 오버플로우가 발생하여 -32768마리로 인식,
총 65535마리가 반대망향으로 넘어가버려서 다시 사실상 0부터 (가장 작은 정수, -32768) 양을 세게 되는 슬픈 만화이다.
문명 간디
평화의 비폭력의 간디가 문명 시리즈에서 폭력의 상징이 된 이유도 바로 오버플로우 때문이다.
옛날 문명시리즈에서 기본으로 적대도가 1로 설정되어 있고, 간디의 경우에는 적대도를 2 낮추는 설정 같은 것이 있다고 한다. (용어는 정확하지 않으나 1과 2는 정확)
1-2 = -1
그러나 적대도 따위가 음수일리는 없다는 생각을 한 개발자가 적대도에 "부호 없는" 정수를 지정,
가장 큰 수인 255로 인식하게 되어 버렸다.
즉, 적대도가 맥시멈이 되어 폭력적으로 변한 것이다.
Y2K문제
20세기 말에 지구가 멸망할 거라는 개소리를 하던 문제이다.
사실 멸망까지는 아니더라도 충분히 컴퓨터를 사용하는 곳에서 장애를 일으킬 수 있을 사건이었다.
문제는 메모리를 절약하기 위해 연월일을 2자리씩만 저장하던데에서 발생한다.
991231
여기서 하루가 지나면?
000101
이 날짜는 1900년인지 2000년인지 알 방법이 없어지게 된다.
이것은 정수형에서 발생한 문제라기 보다는 문자를 1글자씩 (1바이트씩) 저장하는 데에서 발생한 문제.
2038년 1월 19일 03시 14분 07초 문제
오늘날 대부분의 컴퓨터에서는 시간을 저장하기 위해 특정한 시점을 기준으로 흐른 초를 계산하는 타임스탬프를 사용한다.
그 기준은 1970년 1월 1일 0시 0분 0초.
이로부터 2147483647초가 지난 시점이 바로 저 시각이다.
저기서 1초만 지난다면 타임스탬프는 -2147483648로 인식하게 되어 1970년으로 부터 68년 전인 1901년으로 돌아갈 것이다.
다행히 이 문제는 별로 문제가 되지 않을 듯하다.
여기에는 크게 보면 두가지, 작게는 세가지 이유가 있다.
1. 시간은 거꾸로 흐를 일은 없으므로 부호 없는 정수형을 쓴다.
사실 미래만을 본다면 부호 없는 정수형을 쓰면 끝날 문제이다.
하지만.. 옛날 자료를 다루는 곳에서는 치명적일 수 있다.
2. 메모리 가격이 옛날에 비해 매우 싸졌으니 그냥 8바이트 정수형을 쓴다.
이 경우 현재 쓰는 달력을 기준으로, 2292억년 모월 모일에 오버플로우가 발생한다.
이것에 대한 문제는 적어도 우리는 하지 않아도 될 것 같다.
3. 메모리 가격이 옛날에 비해 싸졌고, CPU성능도 좋아졌으니 문자열로 저장한다
오늘날자를 그냥 2015-04-25 23:50:50 이런식으로 저장하는 것이다.
총 19바이트를 사용하고(구분 부호들을 빼면 14바이트), 문자열을 파싱해서 연산하고 다시 문자열로 바꾸는 과정이 필요하지만
오늘날의 컴퓨터 사양으로는 그정도는 그냥 대충..
강남스타일
두유노우강남스타일?
강남스타일의 유튜브 조회수가 부호가 있는 4바이트 정수 최대범위를 넘어서버려서 음수로 출력된 일이 있었다.
유튜브의 개발자가 조회수가 21억이 넘어갈 거라는 생각은 안하고 만들어서 저런 일이 생겼다고 한다.
5. 오버플로우에 잘 대처한 한 사례
일베.
일베의 글들의 번호를 보면 현재 50억대다.
일베에서 어떻게 대처했는 지는 모르지만 잘 대처한 듯 하다.
일베가 현재 사용하고 있는 xe에서는 회원, 글, 댓글 등등을 모두 하나의 일괄적인 번호 체계로 구분한다.
그래서
글 1
댓글 2
회원 3
글 4
글 5
이런식으로 번호가 매겨진다.
그래서 현재 글들의 번호가 50억대고 곧 60억에 돌파하는 것이다.
만약 number 필드의 사이즈를 2배로 늘리면 어떻게 될까?
60억을 기준으로..
60억 : 6기가바이트
60억 * 4바이트 : 24기가바이트
원래 번호를 저장하는 데에만 24기가바이트의 용량을 쓰고 있었다.
그런데 얘네를 하나마다 8바이트씩 할당해주면..
번호들을 저장하는데에만 48기가바이트를 저장하는 것이다.
그리고 어느 놈이 작성했는가? 회원번호의 길이도 두배
어느 글에 어느 댓글이 달렸는가? 글, 댓글 번호의 길이도 두배
거의 모든 것이 두배가 된다.
어떻게 했는 지는 몰라도 잘 처리 하였다.