급수의 수렴 문서의 검증 코드다. if __name__ == "__main__": 아래가 자체 테스트다.
"""급수의 수렴 검증.
문서: 17.급수의 수렴 (예시, 판정법, 증명, 예제, 활용, 카드 C1~C3)
주장 1: 예시 표의 부분합(n = 10, 1000, 10^6)과 극한 π²/6, ln 2로 다가감(실험).
주장 2: 적분 판정의 끼우기 — p = 1, 2, 0.5에서 ∫_1^{n+1} <= S_n <= 1 + ∫_1^n.
주장 3: 예제 Σk²/2^k = 6, Σk/2^k = 2, Σ1/k! = e - 1 (유리수 부분합과 꼬리 한계).
주장 4: 교대급수 오차 |S - S_n| <= b_{n+1} (Σ(-1)^{k+1}/k, n <= 1000).
주장 5: 재배열 1 + 1/3 - 1/2 + 1/5 + 1/7 - 1/4 + ... -> (3/2) ln 2 (실험).
주장 6: 카드 C3 — (k/(k+1))^k -> 1/e, Σ k!/k^k 부분합이 모임. 카드 C2 — 조화급수 묶음이 1/2 이상.
주장 7: 부동소수점 — Σ1/k² (k <= 10^6)를 큰 것부터와 작은 것부터 더하면 끝자리가 다르고, 작은 것부터가 π²/6 - 꼬리에 더 가깝다.
"""
import math
from fractions import Fraction as F
def main():
t = {10: (2.929, 1.550, 0.6456), 1000: (7.485, 1.6439, 0.6926), 10 ** 6: (14.39, 1.644933, 0.693147)}
for n, (a, b, c) in t.items():
h = math.fsum(1 / k for k in range(1, n + 1))
s2 = math.fsum(1 / k ** 2 for k in range(1, n + 1))
al = math.fsum((-1) ** (k + 1) / k for k in range(1, n + 1))
digits = lambda x: len(str(x).split(".")[1])
assert abs(h - a) < 0.5 * 10 ** -digits(a) + 1e-12
assert abs(s2 - b) < 0.5 * 10 ** -digits(b) + 1e-12
assert abs(al - c) < 0.5 * 10 ** -digits(c) + 1e-12
assert f"{math.pi ** 2 / 6:.6f}" == "1.644934" and f"{math.log(2):.6f}" == "0.693147"
print("[OK] 주장 1: 예시 표")
for p in (1, 2, 0.5):
I = (lambda a, b: math.log(b / a)) if p == 1 else (lambda a, b, p=p: (b ** (1 - p) - a ** (1 - p)) / (1 - p))
for n in (1, 10, 1000, 10 ** 5):
s = math.fsum(k ** -p for k in range(1, n + 1))
assert I(1, n + 1) <= s + 1e-12 and s <= 1 + I(1, n) + 1e-12
print("[OK] 주장 2: 적분 판정의 끼우기")
s = sum(F(k * k, 2 ** k) for k in range(1, 200))
assert 6 - s < F(1, 10 ** 50) and s < 6
s = sum(F(k, 2 ** k) for k in range(1, 200))
assert 2 - s == F(201, 2 ** 199)
s = sum(F(1, math.factorial(k)) for k in range(1, 30))
assert abs(float(s) - (math.e - 1)) < 1e-15
print("[OK] 주장 3·예제: 6, 2, e - 1")
S = math.log(2)
part = 0.0
for n in range(1, 1001):
part += (-1) ** (n + 1) / n
assert abs(S - part) <= 1 / (n + 1) + 1e-15
print("[OK] 주장 4: 교대급수 오차 한계")
total, odd, even = 0.0, 1, 2
for _ in range(10 ** 6):
total += 1 / odd + 1 / (odd + 2) - 1 / even
odd += 4
even += 2
assert abs(total - 1.5 * math.log(2)) < 1e-5 and abs(total - math.log(2)) > 0.3
print(f"[OK] 주장 5: 재배열 합 {total:.6f} ≈ 1.5 ln 2 = {1.5 * math.log(2):.6f}")
for k in (10, 1000, 10 ** 6):
assert abs((k / (k + 1)) ** k - 1 / math.e) < 1 / k
part = [0.0]
for k in range(1, 60):
part.append(part[-1] + math.exp(math.lgamma(k + 1) - k * math.log(k)))
assert abs(part[-1] - part[-10]) < 1e-15
for j in range(1, 20):
assert math.fsum(1 / k for k in range(2 ** (j - 1) + 1, 2 ** j + 1)) >= 0.5
print(f"[OK] 주장 6·카드 C2·C3: Σ k!/k^k ≈ {part[-1]:.6f}, 조화급수 묶음 >= 1/2")
n = 10 ** 6
big_first = 0.0
for k in range(1, n + 1):
big_first += 1 / (k * k)
small_first = 0.0
for k in range(n, 0, -1):
small_first += 1 / (k * k)
ref = math.fsum(1 / (k * k) for k in range(1, n + 1))
assert big_first != small_first and abs(small_first - ref) <= abs(big_first - ref)
print(f"[OK] 주장 7: 순서에 따른 차이 {abs(big_first - small_first):.2e}")
print("ALL CHECKS PASSED")
if __name__ == "__main__":
main()