제곱근 2는 아래와 같이 연분수의 꼴로 나타낼 수 있습니다.
연분수에서 이렇게 끝없이 반복되는 부분은 √2 = [1;(2)]처럼 나타낼 수 있는데, 여기서 (2)는 숫자 2가 반복됨을 뜻합니다. 같은 방법으로 √23은 [4;(1,3,1,8)]이 됩니다. 이 연분수의 부분 합을 구하면, 해당 제곱근의 훌륭한 근사값으로 쓸 수 있습니다.. √2의 수렴 과정을 한 번 보겠습니다.
이런 식으로 처음 10번에 해당하는 값은 다음과 같이 됩니다.
정말 놀라운 사실은 가장 중요한 수학 상수 중 하나인 e가 다음과 같은 연분수 꼴로 나타내어진다는 것입니다.
e = [2;1,2,1,1,4,1,1,6,1,…, 1,2k,1…]
이 경우 수렴 과정의 처음 10번은 이렇습니다.
여기서 열 번째 값의 분자 자릿수를 모두 더하면 1+4+5+7 = 17이 되는 것을 알 수 있습니다. 그러면 e의 100번째 연분수 확장 값의 분자 자릿수를 모두 더하면 얼마가 됩니까?
접근
문제가 길다보니 괜히 골치가 아픈 것 같은 느낌이지만, 바로 직전 문제에서 자연수의 제곱근을 연분수의 형태로 나타내는 것에 알아보았고, 이번에는 특정 단계까지 이 연분수를 풀어서 가분수의 형태로 만들어 나가는 과정을 계산하면 되는 것이다.
앞 문제에서 연분수 전개를 반복 마디로 표현하는 방법에 대해서 배웠다. 2의 제곱근은 [1; 2] 로 표현한다. 그래서 첫번째 단계는 1 + 1/2 로 3/2 이 된다.
두 번째 단계는 다음과 같이 푼다.
- 2 + 1/2 = 5/2 이다.
- 1 + (5/2)-1 이므로 1 + 2/5 = 7/5 가 된다.
만약 세 단계까지 진행한다면, 다음과 같이 계산하게 된다.
- 2 + 1/2 = 5/2
- 2 + 2/5 = 12/5
- 1 + 5/12 = 17/12
즉, 반복마디를 계산하고자 하는 단계까지 나열한 뒤에 역수를 취하고 이것을 이전단계의 값과 반복해서 더해나가는 것이다. 참고로 문제에서 첫단계는 1이었으로, 정수 부분만 취하는 것을 1단계로 봐야하고, N단계까지는 반복마디를 N-1 번까지 확장하면 된다.
그러면 문제를 풀기 위해서는 두 가지 도구가 준비되어야 한다.
- 반복마디를 만들어 줄 제너레이터
- 분수를 계산할 도구.
분수는 파이썬의 fractions.Fraction
타입을 사용하면 간편하다. 분모분자가 얼마든지 커질 수 있고, 자동으로 약분까지 처리해준다.
2의 제곱근의 연분수 전개
우선 문제에서 제시한, 2의 제곱근의 연분수 전개를 구현해보자.
gen()
은 순환마디를 입력받아서 매번 자리의 값을 무한히 만들어주는 제너레이터이다.take()
는 무한 제너레이터에서 n 번째 값 까지를 얻어주는 제너레이터이다.foo()
는gen()
과 같이 순환마디를 생성하는 제너레이터와 n을 입력받아서 n 단계만큼 전개한 값을 분수(Fraction
)의 형태로 리턴한다.
이 함수들을 사용하여 2의 제곱근의 연분수 전개를 1부터 10까지 구해보고, 검증하자.
from typing import Generator, Iterable, TypeVar
from fractions import Fraction
T = TypeVar("T")
def gen(a: T, bs: Iterable[T]) -> Generator[T, None, None]:
yield a
while True:
for b in bs:
yield b
def take(xs: Generator[T, None, None], n: int) -> Generator[T, None, None]:
for _ in range(n):
yield next(xs)
def foo(g: Generator[int, None, None], n: int) -> Fraction:
ls = tuple(take(g, n))[::-1]
res = Fraction(ls[0], 1)
for x in ls[1:]:
res = x + 1 / res
return res
for i in range(10):
g = foo(gen(1, [2,]), i+1)
print(g)
자연대수의 연분수 순환마디
자연대수 e 의 연분수꼴 표기는 [2; 1, 2, 1, 1, 4, 1, 1, 6, 1, …] 이며, 자연수의 제곱근과는 달리 순환마디가 생기지 않는다. 이 수열은 3개씩 끊어서 1, 2k, 1 이 무한히 반복된다 (k = 1, 2, 3,…) 앞서 구현한 함수들은 이러한 제너레이터만 있으면 계산이 가능하니까, 제너레이터만 추가로 구현하면 된다. 길이가 100 밖에 안되기 때문에 답은 즉시 나온다.
def eg():
yield 2
k = 1
while True:
yield 1
yield k * 2
yield 1
k += 1
result = foo(eg(), 100).numerator
print(sum(map(int, str(result)))