03. ループと数式
テーマ: 複利計算・実効年利・「72の法則」の検証
学習点: while ループ, math モジュール, 関数の既定引数, 反復計算と閉形式の比較
依存: 標準ライブラリのみ / 難易度: 基礎
実行方法
uv run 03_compound_interest.py
スクリプト冒頭の PEP 723 メタデータ(# /// script)により、必要なライブラリは
uv が自動的に仮想環境へ導入します。事前の pip install は不要です。
解説
何をするプログラムか
複利は「利息が利息を生む」仕組みで、金融の最も基本的な計算です。このスクリプトは、元本 100 万円を年利 3% で 20 年運用したときの将来価値を、(1) 公式(閉形式)、(2) 毎年利息を積み上げるループ、(3) 連続複利の 3 通りで計算して突き合わせます。
続いて、名目年利 5% のまま複利の回数を年 1 回 → 半年 → 四半期 → 毎月 → 毎日と増やすと実効年利がどこまで上がるかを観察し、最後に「元本が 2 倍になる年数 ≒ 72 ÷ 金利(%)」という有名な暗算法「72 の法則」の精度を、対数を使った厳密解と比較して検証します。
コードの読みどころ
future_value(pv, rate, years, n=1)は既定引数n=1を持ち、呼び出し側が複利回数を省略すれば年 1 回複利になります。式は閉形式pv * (1 + rate / n) ** (n * years)そのままです。future_value_loop()は同じ計算をfor _ in range(years): v *= 1 + rateという反復計算で行います。ループ変数を使わないときにアンダースコア_を置くのは Python の慣用です。閉形式と 1 円単位まで一致することが検算になります。- 連続複利と厳密解には math モジュールを使います。
math.exp(rate * years)が e^(rt)、years_to_double()のmath.log(2) / math.log(1 + rate)が ln2 / ln(1+r) です。 - 複利頻度の比較は
for n, label in [(1, "年1回"), (2, "半年"), ...]と、タプルのリストを回してアンパックする読みやすい書き方です。 - 72 の法則の検証表では、厳密解と近似
72 / (r * 100)を並べ、f 文字列の{r:>6.0%}や{exact:>10.2f}で桁を揃えています。
理論的背景
年 n 回複利では、1 年間で元本は (1 + r/n)^n 倍になるため、実効年利は (1+r/n)^n − 1 です。n を無限に大きくした極限が連続複利で、微積分で学ぶ極限公式 lim (1+r/n)^n = e^r により実効年利は e^r − 1 に収束します。つまり複利回数をいくら増やしても実効年利には上限があります。
元本が 2 倍になる年数は (1+r)^t = 2 を解いた t = ln2 / ln(1+r) です。r が小さいとき ln(1+r) ≒ r なので t ≒ 0.693/r、つまり「69.3 ÷ 金利(%)」ですが、実用では約数が多く暗算しやすい 72 が使われます。72 を使うことで、r がある程度大きい領域でも誤差が相殺されます。
実行結果の見方
最初のブロックでは閉形式とループがともに 1,806,111 円で一致し、連続複利は 1,822,119 円とわずかに大きくなります。複利頻度の表では、実効年利が 5.000% → 5.127% へと増えるものの、毎日複利(5.12675%)と連続複利(5.12711%)はほぼ同じで、「上限」への収束が数値で見えます。
72 の法則の表では、誤差は年利 1% で +2.34 年ある一方、8% では −0.01 年とほぼ完璧です。末尾の注記どおり、年利 6〜10% 付近で最も精度が高い近似であることが確認できます。
ソースコード
# /// script
# requires-python = ">=3.11"
# dependencies = []
# ///
"""03: ループと数式 -------------------------------------------------------
テーマ: 複利計算・実効年利・「72の法則」の検証
学習点: while ループ, math モジュール, 関数の既定引数, 反復計算と閉形式の比較
根拠: 年n回複利の実効年利は (1+r/n)^n - 1、連続複利では e^r - 1 に収束する。
「72の法則」は ln2/ln(1+r) の近似(r が小さいとき ln(1+r)≒r より 0.693/r)。
"""
import math
def future_value(pv: float, rate: float, years: int, n: int = 1) -> float:
"""年利rate・年n回複利でyears年後の将来価値(閉形式)。"""
return pv * (1 + rate / n) ** (n * years)
def future_value_loop(pv: float, rate: float, years: int) -> float:
"""同じ計算をループで(1年複利)。閉形式と一致することを確認する。"""
v = pv
for _ in range(years):
v *= 1 + rate
return v
def years_to_double(rate: float) -> float:
"""元本が2倍になる年数(厳密解)。"""
return math.log(2) / math.log(1 + rate)
def main() -> None:
pv, rate, years = 1_000_000, 0.03, 20
print(f"元本 {pv:,}円 / 年利 {rate:.1%} / {years}年")
print(f" 閉形式 : {future_value(pv, rate, years):>14,.0f} 円")
print(f" ループ : {future_value_loop(pv, rate, years):>14,.0f} 円")
print(f" 連続複利: {pv * math.exp(rate * years):>14,.0f} 円")
print("\n[複利頻度と実効年利] 名目年利 5%")
r = 0.05
for n, label in [(1, "年1回"), (2, "半年"), (4, "四半期"),
(12, "毎月"), (365, "毎日")]:
print(f" {label:<6} 実効年利 {(1 + r / n) ** n - 1:.6%}")
print(f" 連続複利 実効年利 {math.exp(r) - 1:.6%} <- 上限")
print("\n[72の法則の精度検証]")
print(f" {'年利':>6}{'厳密解':>10}{'72の法則':>10}{'誤差':>9}")
for r in [0.01, 0.02, 0.03, 0.05, 0.08, 0.12, 0.20]:
exact, approx = years_to_double(r), 72 / (r * 100)
print(f" {r:>6.0%}{exact:>10.2f}{approx:>10.2f}{approx - exact:>9.2f}")
print(" ※ 年利6-10%付近で近似精度が最も高い。")
if __name__ == "__main__":
main()
実行結果
元本 1,000,000円 / 年利 3.0% / 20年
閉形式 : 1,806,111 円
ループ : 1,806,111 円
連続複利: 1,822,119 円
[複利頻度と実効年利] 名目年利 5%
年1回 実効年利 5.000000%
半年 実効年利 5.062500%
四半期 実効年利 5.094534%
毎月 実効年利 5.116190%
毎日 実効年利 5.126750%
連続複利 実効年利 5.127110% <- 上限
[72の法則の精度検証]
年利 厳密解 72の法則 誤差
1% 69.66 72.00 2.34
2% 35.00 36.00 1.00
3% 23.45 24.00 0.55
5% 14.21 14.40 0.19
8% 9.01 9.00 -0.01
12% 6.12 6.00 -0.12
20% 3.80 3.60 -0.20
※ 年利6-10%付近で近似精度が最も高い。