개발자라면 꼭 알아야 할 숫자 모음
0. 목차
1. 들어가며
인간은 의사소통할 때 숫자가 반드시 필요하지 않다. 내가 같이 식사 중인 A에게 ‘자네, 거기 물통 좀 건네주겠나?’라고 물을 때 이 문장은 수학암호 등으로 인코딩할 수야 있겠지만, 나와 A는 말하고 이해할 때 수학적인 의미를 전혀 담고 있지 않다. 인간의 통상적인 의사소통이 다 그렇다.
하지만 컴퓨터는 그렇지 않다. 개발을 조금만 공부해봐도 배우는 ‘컴퓨터에게는 모든 것이 숫자다’라는 명제는 참이다. 컴퓨터는 0과 1의 이분법적인 값만을 취하는 비트를 정보의 최소단위로 하며 이를 조합해 더욱 큰 데이터를 구성해나간다. 그렇게 해서 이 장의 첫 글자인 ‘인’이라는 글자도 컴퓨터 내부에서는 숫자로 처리되고 있다. 파이썬에서 확인해보니 ‘인’이라는 글자는 유니코드 코드 포인트가 51064이다. 내가 입력하는 화면에는 사람이 읽을 수 있는(human-readable) 글자가 출력됐지만 컴퓨터 내에서는 본질적으로 숫자로 취급되고 저장되고 인식되는 것이다.
하지만 컴퓨터로 게임이나 일상 문서작업만을 한다면 이런 원리는 정말 몰라도 된다. ‘!’의 유니코드 포인트가 33라는 것이 뭐가 그리 중요하겠는가? 엑셀에서 표를 더 이쁘게 표현하는 게 훨씬 중요하지.
그러나 개발을 공부하는 사람의 입장이라면 입장은 달라진다. 일반 유저는 컴퓨터에게서 대접 받는 입장이다. 컴퓨터가 사람에게 맞춰서 사람이 읽을 수 있는 평문(plain text)으로 인터페이스를 제공하고, 유저는 그것을 통해 컴퓨터를 ‘조작’한다. 그러나 개발을 공부하고 컴퓨터를 좀더 들여다봐야 한다면 숫자에 익숙해져야 한다. 사용하는 개발 언어가 인간에게 보다 친숙한 고급 언어라 할지라도 이는 변하지 않는다. 개인적인 예를 들어보자. 나는 알고리즘을 공부하면서 완전탐색을 사용한 해답을 고려할 때가 있다. 완전탐색은 입력의 크기 \(n\)에 따라 보통 \(n!\)의 시간복잡도를 갖기 때문에 완전탐색을 사용하겠다면 문제의 입력의 상한을 살펴보는 것이 이롭다. 만약 상한이 10 미만의 자연수라면 완전탐색은 충분히 사용가능한 기법일 수 있지만 10 이상이 되면 사용하기 어렵다. 10!만해도 360만을 넘는 매우 큰 값이기 때문이다. 만약 내가 \(10!\)이 3,628,800이라는 것을 외우고 있지 않다면 입력에 따라 팩토리얼의 복잡도가 얼마나 될지 가늠하기 쉽지 않을 것이다. 또 네트워크를 공부할 때는 어떤가? 2의 32승이 43억에 가까운 수라는 것을 정확히 인지하지 못하면 현대사회에서 IP 주소가 고갈되는 문제를 이해할 수 없을 것이다.
결론은 개발을 공부하고, 알고리즘이나 시스템을 설계하고 기존 시스템의 성능을 분석할 때 여러 숫자 상수에 익숙하면 좋다는 것이다. 개인적으로 숫자를 좋아하기도 해서, 나는 네트워크나 알고리즘을 공부할 때 자주 만져보거나 찾아봐야 했던 숫자들을 조금 외우고 있다. 그래서 이번 포스트의 목적은 이를 공유하고 개인적으로도 저장하고 업데이트하는 데 있다.
이후 각 장에서는 특정 \(n\)에 따른 로그, 팩토리얼, 지수, 기타 상수를 알아볼 것이다. 이는 경험적으로 내게 유용했거나 또는 현대사회에서 중요한 의미를 갖는 상수들이다. 이중에는 내가 툭 건드리면 술술 나올 정도로 외운 것도 있고, 너무 길다거나 매우 중요하지는 않아 외우지 않은 것도 있다. 이를 구분해서 개인적으로 꼭 외웠으면 하는 숫자에서는 강조 표시를 하도록 하겠다. 들어가자.
2. Log 상수(\(log_{10}n\))
밑이 10인 로그를 상용로그(Common Logarithm)이라고 한다. 인간에게는 10진수가 익숙하고 따라서 밑을 10으로 하는 로그 계산이 일반적(common)이여서 그런 이름이 붙지 않았나 한다.
상용로그에서 이 수를 외울 것을 추천한다.
[\begin{array} \label{}
\mathbf{log_{10}2 \approx 0.3010}
\end{array}]
뭐 당연한 숫자다. 고등학교 수학만 공부해도 한 번씩은 접하는 숫자다. 이 숫자는 2는 10의 0.3010승이라는 것을 의미한다. 이 숫자는 대관절 왜 외우라는 것일까?
이 숫자는 알고리즘을 공부할 때 2의 지수승이 대략 어느 정도의 큰 수인지 가늠할 때 사용한다. 가령 어떤 문제에 있어 내가 작성한 알고리즘의 시간복잡도가 \(O(2^n)\)라고 해보자. 그리고 \(n\)의 상한이 100이라고 했을 때 이 알고리즘은 일반적으로(내 구린 노트북으로) 실행할만한가?
이 문제를 맞닥뜨렸을 때 순진하게 \(2^{100}\)을 계산하지는 말자. 이 정도로 지수가 커지면 눈으로 길이조차 가늠할 수 없고, 또 구체적인 숫자는 중요해지지 않는다. 이때 \(log_{10}2\) 값을 이용해 저 무식하게 큰 수의 자릿수 정도만 파악하자. 저 수를 10진수로 변환했을 때의 자릿수를 다음과 같이 구할 수 있다.
[\begin{array} \label{}
\mathbf{log_{10}2^{100} = 100log_{10}2 \approx 100 \times 0.3010 = 30.10}
\end{array}]
음, 저 수가 어떤 수일지 잘 모르지만 확실한 건 저 수는 1조(\(10^{12}\))에 1조를 곱하고 거기에 1백만(\(10^{6}\)) 정도를 더 곱해야 나오는 무시무시한 수라는 것이다.
그래서 우리는 현재의 입력 상한에서 현재의 시간복잡도를 갖는 해결책으로는 문제가 해결되지 않을 것이라고 합리적인 추측을 할 수 있다.
이 추측을 할 때 \(log_{10}2 \approx 0.3010\)라는 것을 외우고 있다면 저 계산을 1초만에 할 수 있다. 이런 경우를 대비해서 저 수를 외울 것을 추천한다. 숫자도 작고 효용도 있다.
자매품으로 이 숫자도 있다.
[\begin{array} \label{} log_{10}3 \approx 0.4771 \end{array}]
\(log_{10}2\)보다는 확실히 필요가 적지만 숫자가 워낙 작아서 외워볼만 하다.
3. 팩토리얼 상수(\(n!\))
앞서 첫 장에서 완전탐색 알고리즘이 많은 경우 \(O(n!)\)의 시간복잡도를 갖는다는 것을 확인했다. 따라서 이 알고리즘을 사용하려면 문제의 입력의 상한을 확인해야 한다.(사실 문제를 보는 순간 입력의 조건을 어딘가에 기록하는 습관을 들여야 한다.)
외우고 있으면 좋은 입력의 크기(\(n\))에 따른 팩토리얼 상수는 다음과 같다.
[\begin{array} \label{}
5! = 120
8! = 40,320
\end{array}]
이 두 수는 무난해서 외울만하다. 내 경험상 이 두 팩토리얼은 간단한 수 계산을 할 때 필요했던 것 같다. 그리고 중요한 건 이 숫자다.
[\begin{array} \label{} \mathbf{10! = 3,628,800} \end{array}]
\(10!\)은 약 360만에 달하는 숫자이다. 내가 볼 때, \(O(n!)\)의 시간복잡도를 갖는 알고리즘의 입력의 상한은 10 정도이다. 그 이후부터는 너무너무 커져서 동적계획법이나 분할정복 같은 대안을 찾아봐야 한다. 하지만 10 이하까지는 어떻게 비벼볼만 하다.
이 Sorting Game은 완전탐색을 사용하는 문제의 예시로 Algospot에서 가져왔다. 이 문제에서 배열의 길이의 상한이 8이라는 것은 굉장히 의미심장하다.
팩토리얼에서는 이 세 숫자는 외우면 좋겠다.
4. 지수 상수(\(2^n\))
지수 상수는 중요하다. C 언어 등에서 각종 자료형의 크기에 따른 데이터의 상하한, \(O(2^n)\)의 시간복잡도를 갖는 알고리즘을 다룰 때, 특히 네트워크 IP 등을 공부할 때 지수 상수를 접하게 된다.
그래서 여기서는 여러 지수 상수를 살펴보고 이를 외우라고 강요한다. 밑수는 2로 고정한다. 개인적으로 2가 아닌 다른 밑수를 써본 기억이 없다.
[\begin{array} \label{}
\mathbf{2^8 = 256}
\end{array}]
우리는 일반적으로 1바이트는 8비트라고 알고 있다. 하지만 수십년 전에는 1바이트는 6, 7 등 8이 아닌 다른 수의 비트를 가진 때가 있었다. 그래서 8비트를 의미할 때 전문적인 표현으로는 이를 옥탯(octet)이라고 한다. C 언어의 char 자료형도 1바이트로 알고 있다. RGB의 각 색도 8비트로 표현된다.
그렇기 때문에 8비트의 표현범위를 아는 것은 의미 있다. 2의 8승은 256으로 총 256의 정보 상태를 저장할 수 있다.
[\begin{array} \label{}
\mathbf{2^{10} = 1,024}
\end{array}]
2의 10승은 1024로 1000에 근사한다. 그래서 외울만한 가치가 있다. 그리고 외우기도 쉽다. 2의 10승인 1024이 ‘10’으로 시작하기 때문이다. 이 숫자가 1000에 근사한다는 것은 큰 장점이 있는데, 이를 통해 2의 20승은 1백만(\(10^6\)), 2의 30승은 10억(\(10^9\)) 정도로 근사할 것이라고 추측 가능하기 때문이다.
참고로, 2의 20승은 1,048,576인데 엑셀에서 한 시트가 가질 수 있는 최대 행의 개수가 이 숫자와 같다. 확인해보라. 어떤 이유가 있을 것 같지 않은가?
[\begin{array} \label{} \mathbf{2^{32} = 4,294,967,296} \end{array}]
그 중요한 2의 32승이다. C 언에서 int 자료형과 float 자료형 등은 4바이트의 크기를 갖는데 4바이트는 32비트다. IPv4의 IP 주소는 32비트 수로 표현될 수 있으며 따라서 이론상 할당될 수 있는 전세계 네트워크의 모든 기기의 수는 43억을 넘지 못한다. 이런 이유로 인터넷이 폭발적으로 성장한 현대 사회에서 IP 주소의 고갈의 문제가 대두되었고 NAT 등의 땜빵, IPv6라는 진지한 대안과 같은 여러 해결책이 제시되었다. 2의 32승의 활용은 이것말고도 더 많을 것이다.
이 수는 외우자. 나도 그냥 외웠다. 13자리의 수로 단박에 외워지지는 않는다. 중간과 끝에 ‘96’이 두 번 들어간다는 패턴을 이용해서 한 10번 되뇌이고 실수하다보니 외워지게 되었다.
[\begin{array} \label{}
2^{64} = 18,446,744,073,709,551,616
log_{10}2^{64} \approx 19.2659
\end{array}]
C 언어에서 double 자료형은 8바이트이다. 그래서 2의 64승도 찾아보면 좋겠지만 이 숫자는 너무 길다(1조 X 1천만)… 내 머리로는 다 외우지는 못할 것 같고, 대략 20자리 수라는 것만 기억하면 되겠다.
5. 기타 상수
이제는 개발과는 큰 상관은 없는, 알면 현실세계에서 간단한 계산을 할 때 유용한 상수를 하나 소개할까 한다. 개인적으로 매우 애용하고 있다.
[\begin{array} \label{} \mathbf{12 \times 15 = 180} \end{array}]
이 숫자는 구구단의 범위를 애매하게 벗어나서 평범한 지능을 가진 나는 한 번에 튀어나오지는 않는 수이다. 그래서 외웠다. 바로 어제 12일, 친구와 이런 이야기를 했다. ‘스타트업에서 월 150을 받으면 연봉이 얼마지?’ 이 숫자를 외우면 1800만원이라고 그냥 튀어나온다.
또 이 숫자를 외우면 이를 응용해서 ‘18 * 15’, ‘24 * 15’도 바로 튀어나올 수 있다. 전자는 180에 1.5를 곱해주고(270), 후자는 2만 곱해주면 되기 때문이다(360). 개인적으로 이렇게 응용 가능성이 큰 수를 좋아한다.(그래서 12를 종아한다.)
6. 마치며
내가 좋아하고 외우고 있는 숫자들을 정리해봤다. 네트워크와 알고리즘을 주로 공부하다보니 관련된 숫자를 알고 있으면 유용한 경우가 많았고, 숫자를 좋아하는 성격과 맞아서 작업해봤다.
이 포스트는 의미 있는 숫자를 또 발견할 때마다, 그래서 외울 때마다 업데이트하면 좋을 것 같다. 간만에 포스트 주제가 매우 가벼웠다. 모든 포스트가 이랬으면 소원이 없을텐데.
이상 포스트를 마칩니다.