스타개발자 넓군은 int형이 21억까지 밖에 담을 수 없다는 사실에 짜증나서
Number형을 쓰기로 했다.
100억, 1000억이 되도 프로그램은 잘~돌아갔다.
근데 어느날 에러가 빵빵터져서 값을 살펴보니
서버에서 날라온 값이 1600000000010000253 이었다.
근데 실제로 Number 변수에 담긴 값은 전혀 다르게 1600000000010000100 였던것이다.
Number는 큰 수를 담을 수 있다기에 쓴거였는데 이게 왠일?
여기서 의문이 들었다.
"int 대신 Number를 쓸 경우 어느 정도까지가 안전한걸까?"
Number는 64비트 부동 소숫점 방식을 사용한다.
총 8바이트 = 64비트를 사용한다.
즉, 64개의 on/off로 표현된다는 얘기다.
첫번째 비트 하나는 음수인지 양수인지를 판단하는데 사용되고 (아래에서
s 라고 사용한다)
그 다음 11비트를 지수로 사용된다. (아래에서
n 이라고 사용한다)
그 다음 남는 52비트를 소수로 사용한다. (아래에서
k 라고 사용한다)
k 는 소숫점을 나타내는데 1 + ( 1 /
k ) 로 쓰인다.
k 가 2 라면 1 + ( 1 / 2 ) = 1.5 라는 뜻이다.
편의상 아래에서는 1.
k 라고 표현하겠다.
즉 Number 숫자 하나는 다음과 같이 표현된다.
s * ( 2^
n ) * 1.
k 로 표현된다.
보기 쉽게
( 2^n ) * 1.k
라고 하겠다.
10진수 2를 표현해보면
( 2^
1 ) * 1.
0 = 2 * 1 = 2
즉
n = 1,
k = 0 이다.
9를 표현해보자.
= 8 + 1
= 8 * 1.125
= ( 2^
3 ) * 1.
125
= ( 2^
3 ) * ( 1 + (
0.125 ) )
= ( 2^
3 ) * ( 1 + ( 1 /
8 ) )
= 9
9 하나 표현하는데 이렇게 복잡하다.
그렇다면 Number가 무지 큰 수까지 표현할 수 있다는건 알겠는데
int 보다 더 큰 값을 사용하기 위해서 Number를 쓸 때
과연 얼마나 큰 값까지 정확하게 표현될 수 있을지 알아보자.
1씩 더해가면서 구해보면 되지 않아? 1씩 더해가면서 구해보면 되지 않아?
1씩 더해가면서 찾는 방법은 다음과 같다.
var a: Number = 0;
if( a == ++a )
trace( "찾았다!! " + a );
미리 스포일러를 좀 하자면
위 방법으로 찾을 경우
초당 24fps로 1프레임마다 1000번 연산을 한다고 했을 때
값을 찾을때까지는 약 11900년하고도 8개월이 걸린다.
1만년...
화석이나 남아있으려나 모르겠네....
1씩 더해가면서 구해보면 되지 않아?
자 그렇다면 유추를 해보자
a라는 값을 만들려면 (어떤수 * 1.얼마)로 표현되어야한다.
여기서 핵심은
"1.얼마" 에 있다.
즉,
k 에 답이 있다.
여기서
k 의 비밀을 파헤쳐보겠다.
Number로 표현하는 정수는 다음과 같은 기준이 되는 수가 있다.
2^
0 = 1
2^
1 = 2
2^
2 = 4
2^
3 = 8
2^
4 = 16
...
2^
n = N
여기서 16을 한번 골라서 예를 들어보자.
16을 표현해보면
16 = 2^4 * 1.0
즉
n = 4,
k = 0 이다.
이 16을 기준으로 17, 18, 19를 표현하려면
각각 1, 2, 3씩을 16에 더해주면 된다.
다른말로 하면 즉 1/16, 2/16, 3/16 을 1에 더해서 곱해주면 된다.
17 = 16 * ( 1*( 1/16 ) )
18 = 16 * ( 1*( 2/16 ) )
19 = 16 * ( 1*( 3/16 ) )
이걸
n ,
k 방식으로 바꿔보면 이렇다.
17 = 2^
4 * 1.(
1/16 )
18 = 2^
4 * 1.(
2/16 )
19 = 2^
4 * 1.(
3/16 )
20 = 2^
4 * 1.(
4/16 )
21 = 2^
4 * 1.(
5/16 )
22 = 2^
4 * 1.(
6/16 )
23 = 2^
4 * 1.(
7/16 )
24 = 2^
4 * 1.(
8/16 )
25 = 2^
4 * 1.(
9/16 )
26 = 2^
4 * 1.(
10/16 )
27 = 2^
4 * 1.(
11/16 )
28 = 2^
4 * 1.(
12/16 )
29 = 2^
4 * 1.(
13/16 )
30 = 2^
4 * 1.(
14/16 )
31 = 2^
4 * 1.(
15/16 )
32 = 2^
5 * 1.(
0/32 )
33 = 2^
5 * 1.(
1/32 )
64(2^6)를 기준으로 65, 66을 표현하면
65 = 2^
6 * 1.(
1/64 )
66 = 2^
6 * 1.(
2/64 )
...
127 = 2^
6 * 1.(
63/64 )
이런식으로 나간다.
자세히보면
1/64 여기에 바로 답이 있다.
자 이제 머리를 환기 시키고 새로운 주제로 넘어간다.
위에서 수를 증가시켜주기 위해서 1/16, 2/16, 3/16 표현이 되는데
다음과 같이 풀어볼수 있다.
1 /16 = 1/
16
2 /16 = 1/
8
3 /16 = 1/16 + 2/16
= 1/
16 + 1/
8
4 /16 = 1/
4
5 /16 = 1/16 + 4/16
= 1/
16 + 1/
4
6 /16 = 2/16 + 4/16
= 1/
8 + 1/
4
7 /16 = 1/16 + 2/16 + 4/16
= 1/
16 + 1/
8 + 1/
4
...
15 /16 = 1/16 + 2/16 + 4/16 + 8/16
= 1/
16 + 1/
8 + 1/
4 + 1/
2
뭔가 리듬이 느껴지는가?
모두 1/x 의 조합으로만 표현이 가능하다.
16은 2^
4 , 8은 2^
3 , 4는 2^
2 , 2는 2^
1 이다.
여기에 쓰인
4 ,
3 ,
2 ,
1 이 바로
k 다.
k를 위한 비트수가 52비트라는 얘기는
1/2^
52 + 1/2^
51 + 1/2^
50 ......... 1/2^
1
까지 표현할 수 있다는 뜻이다.
이 숫자들의 배열도 매우 흥미로운 내용으로 채울 수 있지만 오늘의 포스트 주제에 너무 멀어지므로 결론으로 빨리 가보자.
그렇다면 이렇게 1/
k 의 조합으로 얼마나 작은수까지 나눌 수 있을까?
1/
k 의 조합으로 더 이상 1단위로 나눌 수 없을만큼 큰 수가 바로 우리가 찾는 그 수가 아닐까?
1/2^
52 + 1/2^
51 + 1/2^
50 ......... 1/2^
1
바로 이 수가 바로 정수로 1씩 증가했을 때 차곡차곡 나타낼 수 있는 최대라는 뜻이다.
k가 4 일때 나타낼 수 있던 마지막 숫자가
31 = 16 + 15
= 16 + 1*(15/16)
=
15 /16 = 1/16 + 2/16 + 4/16 + 8/16
= 1/
16 + 1/
8 + 1/
4 + 1/
2
위와 같았다.
k=4로는 128, 즉 2^
5 는 나타낼 수 없었다.
k가 4라면 나타낼 수 있는 제일 큰 수는 ( 2^
5 ) - 1 이다.
그렇다면 k를 52까지 나타낼 수 있을때 제일 큰 수는 2^
53 - 1이 될것이다.
즉 2^
53 보다 값이 크다면 정확하게 계산하지 못한다는 뜻이다.
2^
53 은 9,007,199,254,740,992 이다.
테스트를 한번 해보자.
trace( 9007199254740990 );
trace( 9007199254740991 );
trace( 9007199254740992 );
trace( 9007199254740993 );
trace( 9007199254740994 );
이 구문을 플래시에서 실행해보자.
그냥 긁어붙여도 된다.
9007199254740990
9007199254740991
9007199254740992
9007199254740992
9007199254740994
위와 같이 찍혔을 것이다.
Number 형이 9007199254740992가 넘어가면서 k가
52 로는 1씩 계산할 수 없으므로 끝자리가 3을 표현하지 못해 4로 건너뛰어버린것이다.
즉 9007199254740992 까지가 1씩 증가시켰을 때 손실이 없는 보장된 범위라는 뜻이다.
한글로는 9경이다.
생각보다 크지 않다. 막 셀 수 없는 어마어마한수가 아니라 9000억 * 10 이 끝이다.
이 이상을 1씩 카운팅한다면
그 프로그램은 당장 오류가 발생할 것이다.
우리가 플래시를 다루면서 만약 9경을 넘는 수를 정확하게 카운팅해야된다면
Number 역시 답이 아니라는 뜻이다.
Number도 9경이 넘어가면서 1자리에서 손실이 발생하기 시작한다.
자릿수로는 16자리지만 16자리를 풀로 사용할 수 있는게 아니기 때문에
자릿수로는 고작 15자리까지만 보장이 된다는 뜻이다.
숫자가 15자리가 넘어가는 상황이 있다면 반드시 문자열로 다루고
대수연산에 쓰이는 수학라이브러리를 찾아 활용해야한다.
For the better.