기억하기 쉽고 안전한 비밀번호

[latexpage]
‘기억하기 쉽고 안전한’ 비밀번호?

인터넷을 사용하다 보면 어느 서비스든 회원가입을 할 때가 생깁니다. 그리고, 회원가입을 하려면 대부분 비밀번호를 새로 만들어야 합니다. 예전에는 쉽게 만들었던 것 같은데, 대문자 소문자 숫자 특수문자를 다 써서 만들라니 기가 찹니다.

비밀번호 생성기를 켜 봅시다. 인터넷뱅킹에 쓰려고 10자리의 비밀번호를 만들었더니 bet8qU_r#u라는 한 눈에 봐도 강력해 보이는 비밀번호가 나왔습니다. 근데 별로 기억하기는 쉽지 않아 보입니다. 그래도 은행 비밀번호인데 어디다 적어 두자니 남이 볼까 두렵고, 외우자니 시간이 꽤 걸릴 것 같습니다.

‘기억하기 쉬운 비밀번호’와 ‘안전한 비밀번호’는 모순적인 존재들인 것 같습니다. 그런데, 정말 그럴까요?

안전한 비밀번호

  • 이미 비밀번호가 충분히 안전하신 분들께서는 이 부분을 스킵하고 ‘기억하기 쉬운 비밀번호‘부터 보셔도 됩니다!

애초에 ‘안전한 비밀번호’를 왜 만드는 걸까요? 보통은 원하지 않는 사람이 멋대로 내 계정으로 로그인하지 못하게 하기 위함입니다. ‘원하지 않는 사람’이라 함은 비밀번호를 멋대로 알아낼 수 있는 – 일반적으로는 – 해커들이죠.

일단 우리가 가입한 사이트는 비밀번호를 암호화해서 저장한다고 가정합시다. (사실 모든 사이트가 그래야 하지만요. 2017년에 평문으로 비밀번호 저장하는 사이트를 구축하는 웹 개발자는 당장 해고해야 마땅합니다.)

그럼 해커는 어떤 방식으로 비밀번호를 알 수 있을까요? 만약 우리의 암호화된 비밀번호를 입수했다고 해도, 무차별 대입 공격(bruteforcing) 외에는 방법이 없습니다.

암호화는 이런 방식으로 동작합니다. 만약 제가 이 사이트의 관리자 비밀번호를 shift.moe123으로 설정했다고 합시다. 그러면 서버는 (SHA-256 알고리즘을 쓴다면) 비밀번호를 그대로 저장하는 게 아니라, 이런 식으로 저장합니다:

98086156AA56FE8A7D5AC35D4EA21A49A776505B869CC76829CCD93BAC91A3F9

이것을 해시라고 합니다. 해시는 비밀번호마다 다릅니다. 그래서 서버가 맞는 비밀번호인지 체크하려면 사용자가 입력한 비밀번호를 해시로 만들어서 그 값이 똑같은지 체크합니다. 예를 들어 shift.moe123의 해시 값은 항상 위의 해시 값과 같습니다.

그리고 비밀번호가 조금만 바뀌어도 해시 값은 몰라보게 변합니다. 가령 딱 한 글자만 바꾼 shift.moe124의 SHA-256 해시는

66CFE1B558388800107E5E0CE4AADE6866F8FCE147D7D41108D7E930AD923DD5

입니다. 위의 해시와 아래 해시는 전혀 연관이 없어 보입니다.

해시를 통해 원래 비밀번호를 알 수 있는 방법은 … 두 가지가 있습니다. 무작위로 대입해 보는 방법과 암호화 알고리즘을 분석해 알아내는 방법이 있는데요, 후자는 현재로서는 몇십 년동안 천문학적인 액수를 투자해 해시 하나를 풀 수 있습니다. 해커들은 당연히 그럴 만한 가치를 느끼지 못하고 무작위 대입을 시작하는 것입니다.

무작위 대입 전략

해커가 만약 사이트의 모든 유저에 대한 정보 – 비밀번호 해시 값을 포함해서 – 를 담고 있는 데이터베이스를 입수했다고 합시다. 이 때 타겟은 누구일까요? 보통은 어떻게든 해시 값을 생성해서 얻어걸리는 사람의 계정을 탈취해 갈 것입니다. 어떤 계정이든, 로그인만 할 수 있다면 그걸로 카페에 들어가서 게시글을 도배하든 금융거래를 하든 나름대로의 의미가 있을 테니까요.

일단 해커는 먼저 사람들이 가장 자주 사용하는 비밀번호들부터 대입해 볼 것입니다. 1234, 123456, qwerty 같은 비밀번호들이 대표적인 예입니다. 가장 먼저 얻어걸리는 비밀번호들이죠. 위 사이트에 의하면, 아직도 저 리스트에 있는 상위 1,000개의 비밀번호를 전체 91%의 사용자들이 사용하고 있다고 합니다. 보통 SHA-256 키 하나를 대입하는 건 초당 260만번 할 수 있으니 위의 리스트에 있는 비밀번호를 사용 중이라면 고작 0.001초도 안 되어 전부 드러나버릴 수 있습니다.

그런데 요즘은 사이트에서 회원가입을 할 때부터 이런 비밀번호를 못 쓰게 막고 있습니다. 그런 사이트에서는 저런 비밀번호를 사용하는 사람이 애초에 없다는 것일 테고, 해커가 얻어갈 수 있는 이득도 없다는 거나 마찬가지입니다.

그런 해커들이 다음으로 시도해 보는 게 무작위 단어 대입입니다. 사전에 있는 단어들을 조합해서, 처음부터 끝까지 대입해 보는 것이죠. 만약 사전에 있는 단어만으로 만들어진 비밀번호라면 사전에 적혀 있는 단어 수에 따라 다르겠지만, 영어 단어 30만개를 대입한다면 단어 한 개짜리 비밀번호는 대략 0.115초만에, 단어 두 개짜리 비밀번호는 길게는 9시간 35분까지 걸리겠네요. 다시 말하지만 해커는 수많은 정보 중 얻어걸리기만 하면 되기 때문에 9시간 35분은 투자할 가치가 있는, 굉장히 짧은 시간입니다.

무작위 단어 대입을 해도 별 수확을 못 얻었다면 모든 문자를 무작위로 대입하기 시작합니다. 그런데 이건 단점이 조금 있습니다. 비밀번호가 한 자리수 늘어날 때마다 시도해야 되는 가짓수도 배로 늘어난다는 겁니다.

만약 숫자로만 된 비밀번호를 먼저 때려맞추자고 합시다. 숫자는 총 10개가 있으니까 한 자리에 올 수 있는 글자가 10개입니다. 그러니까, 한 자리수 비밀번호를 맞추려면 10번 시도하면 됩니다. 두 자리수는 $10 \times 10 = 100$번 시도하면 되겠군요, $n$자리수의 비밀번호에 대해서는 $10^n$번 시도하면 풀리겠죠?

초당 260만번 대입할 수 있다고 했으니, 4자리 비밀번호는 $\dfrac{10^4}{2,600,000} \approx 0.000385$초, 8자리 비밀번호는  $\dfrac{10^8}{2,600,000} \approx 38.466$초가 걸리겠군요. 한 자리가 늘어날 때마다 걸리는 시간은 10배씩 늘어날 겁니다. 그럼 여기에 알파벳 소문자를 섞으면 어떨까요?

알파벳 소문자는 총 26문자가 있으니까 이걸 숫자와 섞으면 한 자리에 총 36개의 문자가 올 수 있고, $n$자리수의 비밀번호에 대해서는 $36^n$번 시도하면 풀리겠습니다. 4자리 비밀번호는 $\dfrac{36^4}{2,600,000} \approx 0.6460$초, 8자리 비밀번호는  $\dfrac{36^8}{2,600,000} \approx 1,085,042.3$초, 즉 12일 13시간 24분 가량이 걸립니다. 같은 8자리인데, 숫자만 썼을 때는 1분도 안 되던 게 소문자만 섞었는데도 2주가 가까히 걸리게 되었습니다.

그럼, 걸리는 시간을 표로 정리해 보겠습니다.

글자 수 4글자 8글자 10글자 12글자
숫자 10 0초 38초 1시간 4분 4.45일
소문자 26 0초 22시간 18분 1.72년 1,163년
숫자
+ 소문자
36 1초 12.56일 44.59년 5.78만 년
숫자
+ 소문자
+ 특수문자(8)
44 1초 62.54일 331.7년 64.2만 년
숫자
+ 소문자
+ 대문자
62 6초 2.66년 1.02만 년 3935만 년
숫자
+ 소문자
+ 대문자 +
특수문자(8)
70 9초 7.03년 3.45만 년 1.69억 년

이런 이유로 많은 사이트들은 숫자, 소문자, 대문자, 특수문자를 섞어서 비밀번호를 만들 것을 권장하고 있습니다.

문제가 있다면, 숫자, 소문자, 대문자, 특수문자를 전부 섞어서 비밀번호를 정말 복잡하게 만들었는데, 이걸 기억하기가 어렵다는 것입니다.

기억하기 쉬운 비밀번호

그러면 ‘기억하기 쉬운’ 건 무엇일까요?

사람마다 기억하기 어렵고 쉬운 것은 다르겠지만, 일반적으로는 비밀번호에 어떤 의미를 부여하면 기억하기 쉬워지는 것 같습니다. 아무 의미도 없는 이상한 문자열 말고, 예를 들어.. 제가 지금 배고프니까 음식 이름으로 비밀번호를 만들어 봅시다. 비밀번호를 뚫는 데 얼마나 걸릴지는 여기에서 테스트해 볼 수 있습니다.

최근 가격인상으로 말 많은 BBQ 치킨입니다. #Golden_Olive_Chicken이라는 비밀번호를 생각해낼 수 있겠습니다. 위 사이트에 넣어 본 결과 이 비밀번호를 뚫는 데는 1해 7,600경($=1.76\times10^{20}$)년이 필요하다고 합니다. 숫자는 없지만 대소문자와 특수문자가 적절하게 섞였고, 기억하기 쉬우면서 무려 21글자나 됩니다. 배고플 때는 로그인하기 조금 그렇겠지만, 충분히 강력한 비밀번호 같네요!

혹은 노래 가사에서도 비밀번호를 생각해낼 수 있을 것 같습니다.

벌써 발표된 지 4년이 넘어간 곡입니다. 시간 참 빨리 가는 것 같습니다.. I'm_a_mother_father_gentleman_130412 정도를 생각할 수 있을 것 같습니다. 가사만으로는 예측하기 쉬울 수도 있으니 뒤에 발매일을 붙여줬습니다. 특수문자, 대소문자, 숫자 모두가 포함된 36글자의 강력하고 기억하기 쉬운 비밀번호입니다. $1.82\times10^{53}$년이 걸려야 뚫을 수 있습니다.

혹은, 한글 문장을 키보드로 그냥 쳐서 비밀번호를 만들 수 있습니다. 예를 들어.. ‘웃으면서 미래의 이야기를 합시다’는 dntdmaustj_alfodml_dldirlfmf_gkqtlek 정도가 되겠네요, 소문자와 특수문자밖에 없지만 충분히 길기 때문에 $9.0\times10^{39}$년이 걸립니다.

쌍자음이 있으면 대문자를 만들 수 있으니까(영어 키보드는 Shift를 누르면서 글자를 누르면 대문자가 됩니다) ‘대한민국의 주권은 국민에게 있다’는 eogksalsrnrdml_wnrnjsdms_rnralsdprp_dlTek가 됩니다. 무려 41글자, $5.86\times10^{56}$년이 걸리는군요!

이런 식으로 기억하기 쉽고 강력한 비밀번호를 만들 수 있습니다. 취약하거나 기억하기 어려운 비밀번호들은 지금 다시 만들어보는 게 어떨까요?

컴퓨터는 삼각함수를 어떻게 계산하는가

주의: 이 포스트는 약간의 미적분학 지식이 있어야 이해하기 쉽습니다. 그래도 설명을 위해 약간의 증명은 들어가 있으니, 아는 부분은 스킵하셔도 됩니다.

우리는 언제나 직각과 직사각형이 편합니다. 인류는 직교좌표계를 발명했습니다. 그리고 컴퓨터 모니터의 픽셀 배열은 대부분 가로세로 수백~수천 개 픽셀로 이루어진 격자로 이루어져 있고, 이는 직교좌표계로 접근할 수 있습니다. 직교좌표계로 화면을 그리면 픽셀 하나하나를 관리하기가 제일 쉽고 자유로워서가 아닐까 싶습니다.

만약에 누군가가 사칙연산만 제공되는 엔진으로 게임을 개발한다고 생각합시다. 직교좌표계 위에 그려진 어떤 도형이 있는데 이 도형을 회전시켜야 한다고 합니다. 어떻게 하면 될까요?

사실 수학은 이미 답을 알고 있습니다. 어떤 픽셀의 좌표 \(\left ( x, y\right )\)에 대해 회전된 좌표 \(\left ( x′, y′\right )\)는
\[\begin{bmatrix}\cos \theta & -\sin \theta \\ \sin \theta & \cos \theta\end{bmatrix}\begin{bmatrix}x\\y\end{bmatrix}=\begin{bmatrix}x′\\ y′\end{bmatrix}\]
다른 말로 \[\begin{cases}x′=x \cos \theta-y\sin \theta\\ y′=x \sin \theta+y \cos \theta \end{cases}\] 임이 자명합니다. 이걸 회전하기 전의 이미지의 모든 픽셀의 \(\left ( x, y\right )\)에 대해 한 번씩 해 주면 회전한 이미지가 나옵니다. 참 쉽죠?

사칙연산만 갖고 삼각함수를 계산하라고요?

컴퓨터가 어떻게 삼각함수를 계산하는지를 논하기로 했으니까, 컴퓨터가 할 수 있는 연산만을 이용해야겠습니다. 아주 기본적인 연산만 사용 가능한 프로세서에서 말입니다.

그럼 대체 삼각함수는 어떻게 근사할 수 있을까요? \(\sin\)과 \(\cos\)를 구하면 \(\tan\) 등은 나눗셈으로도 구할 수 있으니까 \(\sin\)과 \(\cos\)를 구하는 데 집중해 봅시다.

삼각함수를 다항함수로 근사하면 되지 않을까요?

다항함수는 사칙연산만으로 계산할 수 있으니 삼각함수의 그래프와 비슷한 다항함수를 만들어서 거기다 집어넣으면 되겠네요!

근데 그 다항함수는 대체 어떻게 구할까요? 여기서 테일러 급수가 등장합니다.

테일러 급수

테일러 급수를 이용하면 어떤 미분 가능한 함수라도 다항함수로 근사해 버릴 수 있습니다. 세상에 그런 흑마법이 존재하냐고요? 네, 놀랍게도 존재합니다!

증명

우선 어떤 함수 \(f^\prime\left(x\right )\)를 \(a\)부터 \(x\)까지 정적분해 봅시다. 미적분의 정의에 의해 아래 식과 같이 표현할 수 있습니다.
\[\int_{a}^{x}f^\prime\left(t\right )\mathrm{d}t=f\left(x\right )-f\left(a\right )\]
그리고 위 식을 변형해 봅시다.
\[\int_{a}^{x}f^\prime\left(t\right )\mathrm{d}t=\int_{a}^{x}\left(-1\right )\left(-f^\prime\left(t\right )\right )\mathrm{d}t\]
이제 \(-1\)을 적분하고 \(-f^\prime\left(t\right )\)를 미분해 부분적분법을 씁시다. 이 때 \(f\left(x\right )\)가 무한히 미분 가능하다면, 부분적분법도 무한히 써 버릴 수 있습니다. 그러니까
\[\cdots = \left.\begin{matrix}\left (-\left ( x-t \right )f^\prime\left ( t \right )- \dfrac{\left( x-t \right )^2}{2}f^{\prime\prime}\left ( t \right )- \dfrac{\left( x-t \right )^3}{6}f^{\prime\prime\prime}\left ( t \right )- \cdots\right )\end{matrix}\right|_{a}^{x}\]
가 됩니다. 이 때 적분변수 \(t\)와 관계없는 \(x\)는 상수취급됩니다. 이 식을 전개해 보면
\[\cdots =\left ( x-a \right )f^\prime\left ( a \right )+ \dfrac{\left( x-a \right )^2}{2!}f^{\prime\prime}\left ( a \right )+ \dfrac{\left( x-a \right)^3}{3!}f^{\prime\prime\prime}\left ( a \right )+ \cdots\\ \]
\[= f\left ( x \right )-f\left ( a \right )\]
마지막으로 \(f\left ( a \right )\)를 이항하면
\[f\left ( x \right ) =f\left ( a \right )+ \left ( x-a \right )f^\prime\left ( a \right )+ \dfrac{\left( x-a \right )^2}{2!}f^{\prime\prime}\left ( a \right )+ \dfrac{\left( x-a \right )^3}{3!}f^{\prime\prime\prime}\left ( a \right )+ \cdots\]
즉 \(f^{\left( n \right)}\)이 \(f\)를 \(n\)번 미분한 함수라고 할 때
\[f\left ( x \right ) = \sum_{n=0}^{\infty}\dfrac{f^{\left ( n \right )}\left ( a \right )}{n!}\left( x-a \right )^n\]
가 유도됩니다. 이 때 \(a\)에 어떤 수를 넣어도 근사됩니다.

그래서 이게 삼각함수랑 무슨 상관이에요!

삼각함수는 무한히 미분 가능합니다. 그러니까, \(f\) 대신 삼각함수를 넣으면 삼각함수를 다항함수로 근사할 수 있습니다. 어떤 삼각함수 \(f\left ( x \right )\)에 대해 \(a\)에 \(0\)을 대입해 보면
\[f\left ( x \right ) =f\left ( 0 \right )+ \left ( x-0 \right )f^\prime\left ( 0 \right )+ \dfrac{\left( x-0 \right )^2}{2!}f^{\prime\prime}\left ( 0 \right )+ \dfrac{\left( x-0 \right )^3}{3!}f^{\prime\prime\prime}\left ( 0 \right )+ \cdots\]
\[=f\left ( 0 \right )+ x f^\prime\left ( 0 \right )+ \dfrac{x^2}{2!}f^{\prime\prime}\left ( 0 \right )+ \dfrac{x^3}{3!}f^{\prime\prime\prime}\left ( 0 \right )+ \cdots\]
인데요, \(f\left ( x \right ) = \sin x\)라고 하고 대입해 보면
\[\sin x =\sin\left ( 0 \right )+ x \sin^\prime\left ( 0 \right )+ \dfrac{x^2}{2!}\sin^{\prime\prime}\left ( 0 \right )+ \dfrac{x^3}{3!}\sin^{\prime\prime\prime}\left ( 0 \right )+ \cdots\]
\[=0+ \left ( x \cdot 1 \right )+ \left ( \dfrac{x^2}{2!}\cdot 0 \right )+ \left ( \dfrac{x^3}{3!}\cdot -1 \right )+ \left ( \dfrac{x^4}{4!}\cdot 0 \right )+ \left ( \dfrac{x^5}{5!}\cdot 1 \right )+ \cdots\]
정리하면
\[\therefore \sin x =x- \dfrac{x^3}{3!}+ \dfrac{x^5}{5!}- \dfrac{x^7}{7!}+ \dfrac{x^9}{9!}+ \cdots\]
마찬가지로
\[\cos x =1-\dfrac{x^2}{2!}+\dfrac{x^4}{4!}-\dfrac{x^6}{6!}+\dfrac{x^8}{8!}+ \cdots\]
이 됩니다. 중간 과정에 비해서 정말 간단한 식이 되었습니다. 더구나, 우리의 궁극적인 목표인 사칙연산만으로 삼각함수 표현하기에 성공했습니다! (팩토리얼은 곱셈의 연속일 뿐이니까요!)

근데, 식이 참… 무한합니다. 이건 어떻게 하면 좋을까요? 우리는 어차피 floating-point 변수들은 굉장히 정확하지 않다는 걸 잘 알고 있습니다. 그러니까 저 식을 어디까지만 계산하고 그 이후의 오차는 무시해 버려도 됩니다.

위에서 구한 다항식을 최고차항이 \(n\)인 항까지만 계산하고, 그 이후는 무시해버립시다. 그리고 이걸 \(n\)차 근사식이라고 합시다. 이제부터 차수를 올려나가면서 \(n\)차 근사식의 그래프를 그려보겠습니다.

그래프

▶ 1차 근사식: \(f\left ( x \right ) = x\)

… 하나도 비슷해보이진 않습니다. 다음!

▶ 3차 근사식: \(f\left ( x \right ) = x-\dfrac{x^3}{3!}\)

앞에서는 감을 좀 잠은 거 같기도 한데, 겨우 \(\dfrac{\pi}{2}\)도 가기 전에부터 조금씩 이상하더니 아래로 곤두박질쳐버리는군요. 다음!

▶ 5차 근사식: \(f\left ( x \right ) = x-\dfrac{x^3}{3!} + \dfrac{x^5}{5!}\)

이제 \(\dfrac{\pi}{2}\)에서도 비슷해졌네요! 다음!

▶ 7차 근사식: \(f\left ( x \right ) = x-\dfrac{x^3}{3!} + \dfrac{x^5}{5!}- \dfrac{x^7}{7!}\)

\(pi\) 근처까지도 꽤 비슷해졌습니다. 몇 개만 더 해 봅시다.

▶ 9차 근사식: \(f\left ( x \right ) = x-\dfrac{x^3}{3!} + \dfrac{x^5}{5!}-\dfrac{x^7}{7!} + \dfrac{x^9}{9!}\)

거의 원본 함수와 같아졌습니다. 사실 여기부터는 이제 \(0 < x < \pi\)일 때는 그냥 가져다 써도 오차는 크지 않을 것 같습니다. 어차피 \(sin\)은 주기함수이고 대칭함수이니까 \(0 < x < \dfrac{\pi}{2}\)에서만 정확해도 다른 범위에서는 함수의 특징을 이용해 계산해내면 됩니다.

▶ 11차 근사식: \(f\left ( x \right ) = x-\dfrac{x^3}{3!} + \dfrac{x^5}{5!}- \dfrac{x^7}{7!} + \dfrac{x^9}{9!}- \dfrac{x^{11}}{11!}\)

차이가 얼마나 나는지 점점 이렇게 봐서는 확인하기가 어렵습니다. 마지막으로 13차 근사식까지만 그려보겠습니다.

▶ 13차 근사식: \(f\left ( x \right ) = x- \dfrac{x^3}{3!} + \dfrac{x^5}{5!}- \dfrac{x^7}{7!} + \dfrac{x^9}{9!}- \dfrac{x^{11}}{11!} + \dfrac{x^{13}}{13!}\)

그래프 범위 내에서는 거의 똑같은 곡선이 그려집니다.

프로그래밍 언어에서의 구현 사례

OpenJDKStrictMath 네이티브 코드가 13차 근사식을 통해 삼각함수를 구현하고 있습니다. 정확성을 위해서 13차 근사식을 그대로 사용하진 않고, 이렇게 조금 변형해서 사용합니다.
\[\sin x \sim x- \dfrac{x^3}{3!} + \dfrac{x^5}{5!}- \dfrac{x^7}{7!} + \dfrac{x^9}{9!}- \dfrac{x^{11}}{11!} + \dfrac{x^{13}}{13!}\]
\[\frac{\sin x}{x} \sim 1- \dfrac{x^2}{3!} + \dfrac{x^4}{5!}- \dfrac{x^6}{7!} + \dfrac{x^8}{9!}- \dfrac{x^{10}}{11!} + \dfrac{x^{12}}{13!}\]
그러므로 \(r\)을 이렇게 정의할 때
\[r = x^3 \left(\dfrac{1}{5!} + x^2 \left(-\dfrac{1}{7!} + x^2 \left(\dfrac{1}{9!} + x^2 \left(-\dfrac{1}{11!} + {x^{2}} \cdot \dfrac{1}{13!}\right)\right)\right)\right)\]
\(\sin x\)는 이렇게 표현할 수 있습니다.
\[\sin x \sim x + x^3 \cdot \left(-\dfrac{1}{3!} + x^2 r \right)\]
예를 봅시다. sin 함수에서 __kernel_sin 함수를 호출할 때 코드에서 \(|x| \prec \pi/4\)라면 y, iy의 값은 모두 0입니다. 이외의 범위에서는 적절한 범위로 평행이동시켜 __kernel_sin 혹은 __kernel_cos 함수값을 구합니다.

#include "fdlibm.h"
#ifdef __STDC__
    static const double
#else
    static double
#endif
half = 5.00000000000000000000e-01, /* 0x3FE00000, 0x00000000 */
S1 = -1.66666666666666324348e-01, /* 0xBFC55555, 0x55555549 */
S2 = 8.33333333332248946124e-03, /* 0x3F811111, 0x1110F8A6 */
S3 = -1.98412698298579493134e-04, /* 0xBF2A01A0, 0x19C161D5 */
S4 = 2.75573137070700676789e-06, /* 0x3EC71DE3, 0x57B1FE7D */
S5 = -2.50507602534068634195e-08, /* 0xBE5AE5E6, 0x8A2B9CEB */
S6 = 1.58969099521155010221e-10; /* 0x3DE5D93A, 0x5ACFD57C */

#ifdef __STDC__
    double __kernel_sin(double x, double y, int iy)
#else
    double __kernel_sin(x, y, iy) double x,y; int iy; /* iy=0 if y is zero */
#endif
{
    double z,r,v; int ix;
    ix = __HI(x)&0x7fffffff; /* high word of x */
    if(ix < 0x3e400000) /* |x| < 2**-27 */ {
        if((int)x==0) return x;
    } /* generate inexact */
    z = x*x;
    v = z*x;
    r = S2+z*(S3+z*(S4+z*(S5+z*S6)));
    if(iy==0) return x+v*(S1+z*r);
    else return x-((z*(half*y-v*r)-y)-v*S1);
}

소스에서
\(S1 = -\dfrac{1}{3!} \approx -1.66666667 \times {10}^{-1}\)
\(S2 = \dfrac{1}{5!} \approx 8.33333333 \times {10}^{-2}\)
\(S3 = -\dfrac{1}{7!} \approx -1.98412698 \times {10}^{-4}\)
\(S4 = \dfrac{1}{9!} \approx 2.75573137 \times {10}^{-6}\)
\(S5 = -\dfrac{1}{11!} \approx -2.50521084 \times {10}^{-8}\)
\(S6 = \dfrac{1}{13!} \approx 1.58969099 \times {10}^{-10}\)
으로 근사식의 계수들이 근사되어 하드코딩되어 있는 것을 알 수 있습니다. 또한 \(z=x^2\), \(v=zx=x^3\)으로 계산하고 있습니다. 13차 근사식을 그대로 쓴다면 항마다 1번, 3번, 5번, …, 13번의 곱셈을 해야 하지만 \(x^2\)를 새 상수로 두면 곱셈을 줄일 수 있어서 이런 방법을 사용하지 않았을까 하는 생각입니다.

다른 방법으로도 계산할 수 있나요?

물론 다른 방법들도 있습니다. 예를 들어 볼더가 1956년에 고안한 CORDIC(COordinate Rotation DIgital Computer) 알고리즘을 이용해 삼각함수의 값을 근사하는 것도 가능합니다.

간단하게 설명하자면, 맨 위에서 회전을 하기 위해 곱하는 행렬을
\[\begin{bmatrix}\cos \theta & -\sin \theta \\ \sin \theta & \cos \theta\end{bmatrix}\]
으로 소개했습니다만, CORDIC 알고리즘은 이를 \(\tan\) 함수만으로 표현해
\[R_i = \dfrac{1}{\sqrt{1 + \tan^2 \left(\gamma_i\right)}}\begin{bmatrix}1 & -\tan \left(\gamma_i\right) \\\tan \left(\gamma_i\right) & 1\end{bmatrix}\]
로 변형하고, 컴퓨터가 빠르게 계산할 수 있도록 \(\tan \left(\gamma_i\right) = \pm2^{-i}\)가 되는 \(\gamma_i\), 즉 \(\arctan\left(2^{-i}\right)\)의 값들과 이 때의 \(\dfrac{1}{\sqrt{1 + \tan^2 \left(\gamma_i\right)}}\)의 값들을 하드코딩해 두고 원하는 각도에 가까워질 때까지 \(i\)를 하나씩 증가시키면서
\(\arctan\left(2^{-i}\right)\)를 더하거나 빼면서 행렬 계산을 해나가는 알고리즘입니다.

이 알고리즘은 계산기에 주로 쓰이고 있고, 인텔의 8087 ~ 80486 CPU에도 채용되어 왔습니다.

컴퓨터는 이렇게 삼각함수를 계산합니다.