月收入不随储蓄率变化,所以只计算一次是有意义的:
# calculate income per month over 36 months
base_monthly_salary = 150000 // 12
semiannual_raise = 0.07
monthly_incomes = [base_monthly_salary * (1. + semiannual_raise) ** (month // 6) for month in range(36)]
如果每月的储蓄不赚取利息,问题就小了:
target_amount = 1000000. * 0.25
savings_rate = target_amount / sum(monthly_incomes) # 0.4659859
所以你必须节省 46.6% 的收入。
如果每月的储蓄可以赚取利息,那么问题就更有趣了(绝对是双关语)。
def savings_balance(monthly_incomes, monthly_interest_rate, savings_rate):
total = 0.
for amt in monthly_incomes:
# At the end of each month,
total *= 1. + monthly_interest_rate # we earn interest on what was already saved
total += amt * savings_rate # and add a fixed fraction of our monthly income
return total
我们根据上面的计算来测试一下,
savings_balance(monthly_incomes, 0.0, 0.4659859) # 249999.9467
所以看起来就像我们预期的那样。
您可以将此函数视为迭代评估 36 次多项式。给定已知的monthly_incomes 和interest_rate,我们希望找到savings_rate 以产生所需的total,即找到polynomial - target == 0 的唯一真正正根。如果interest_rate > 0.没有解析解,我们会尝试数值解。
target_amount = 1000000. * 0.25
# Make up a number: annual savings interest = 1.9%
monthly_interest_rate = 0.019 / 12.
# Our solver expects a single-argument function to solve, so let's make it one:
def fn(x):
return savings_balance(monthly_incomes, monthly_interest_rate, x)
def bisection_search(fn, lo, hi, target, tolerance=0.1):
# This function assumes that fn is monotonically increasing!
# check the end-points - is a solution possible?
fn_lo = fn(lo)
assert not target < -tolerance + fn_lo, "target is unattainably low"
if abs(target - fn_lo) <= tolerance:
return lo
fn_hi = fn(hi)
assert not fn_hi + tolerance < target, "target is unattainably high"
if abs(target - fn_hi) <= tolerance:
return hi
# a solution is possible but not yet found -
# repeat until we find it
while True:
# test the middle of the target range
mid = (lo + hi) / 2
fn_mid = fn(mid)
# is this an acceptable solution?
if abs(target - fn_mid) <= tolerance:
return mid
else:
# do we need to look in the lower or upper half?
if target < fn_mid:
# look lower - bring the top down
hi = mid
else:
# look higher - bring the bottom up
lo = mid
现在我们像这样运行它
# From above, we know that
# when interest = 0.0 we need a savings rate of 46.6%
#
# If interest > 0. the savings_rate should be smaller,
# because some of target_amount will be covered by generated interest.
#
# For a small annual_interest_rate over an N year term,
# the effective accrued interest rate will be close to
# N * annual_interest_rate / 2 -> 1.5 * 1.9% == 2.85%
#
# So we expect the required savings rate to be
# about 46.6% * (1. - 0.0285) == 45.3%
bisection_search(fn, 0.40, 0.47, target_amount) # 0.454047973
节省了 45.4%。