Files
SolutionEuler/solutions/0057.SquareRoot/euler_57.py
Sidney Zhang d156541f31 feat(solutions/0057): 添加欧拉计划第57题连分数平方根解法
包含正算递推实现、约分优化及详细数学推导文档
2026-09-16 11:30:19 +08:00

90 lines
2.9 KiB
Python

'''
It is possible to show that the square root of two can be expressed as an infinite continued fraction.
sqrt{2}=1+frac{1}{2+frac{1}{2+frac{1}{2+...}}}
By expanding this for the first four iterations, we get:
begin{aligned}
& 1+frac{1}{2}=frac{3}{2}=1.5 \\
& 1+frac{1}{2+frac{1}{2}}=frac{7}{5}=1.4 \\
& 1+frac{1}{2+frac{1}{2+frac{1}{2}}}=frac{17}{12}=1.41666... \\
& 1+frac{1}{2+frac{1}{2+frac{1}{2+frac{1}{2}}}}=frac{41}{29}=1.41379 ldots
end{aligned}
The next three expansions are 99/70, 239/169, and 577/408, but the eighth expansion, 1393/985,
is the first example where the number of digits in the numerator exceeds the number of digits in the denominator.
In the first one-thousand expansions, how many fractions contain a numerator with more digits than the denominator?
'''
import time
from functools import wraps
from typing import Any, Callable, TypeVar
from math import gcd
F = TypeVar("F", bound=Callable[..., Any])
def benchmark(repeat: int = 1) -> Callable[[F], F]:
if repeat < 1:
raise ValueError("repeat must >= 1")
def decorator(func: F) -> F:
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
total = 0.0
result = None
for _ in range(repeat):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
total += end - start
wrapper.avg_time = total / repeat # type: ignore[attr-defined]
wrapper.total_time = total # type: ignore[attr-defined]
print(
f"[Benchmark] {func.__name__} | repeated {repeat} times | "
f"average: {wrapper.avg_time:.6f}s | total: {wrapper.total_time:.6f}s" # type: ignore[attr-defined]
)
return result
wrapper.avg_time = 0.0 # type: ignore[attr-defined]
wrapper.total_time = 0.0 # type: ignore[attr-defined]
return wrapper # type: ignore[return-value]
return decorator
def compute_square_root(n: int = 1000) -> list[tuple[int,int]] :
"""
Compute the square root of 2 up to n expansions.
"""
cf = [1] + [2 for _ in range(0, n)]
p2, p1 = 0, 1 # p_{-2}, p_{-1}
q2, q1 = 1, 0 # q_{-2}, q_{-1}
results = []
for a in cf:
p = a * p1 + p2
q = a * q1 + q2
p2, p1 = p1, p
q2, q1 = q1, q
if gc := gcd(p, q) != 1:
p //= gc
q //= gc
results.append((p, q))
return results
@benchmark(20)
def main() -> None:
results = compute_square_root(1000)
count = 0
for p, q in results:
if len(str(p)) > len(str(q)):
count += 1
print(f"{count} fractions contain a numerator with more digits than the denominator.")
print(f"sqrt{2} = {results[-1][0]/results[-1][1]:.15f}")
if __name__ == "__main__":
main()