Loan calculator
The monthly payment of a loan, worked out exactly in whole numbers and rounded by a stated rule (to the nearest cent or up to the next), the repayment schedule by year and by month, the last payment that clears the balance to exactly 0.00, and the total paid and total interest - from a text menu.
Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.
About
Works out a loan's monthly payment and its whole repayment schedule: the payment itself, then year by year and month by month how much of each payment is interest and how much pays off the loan, the last payment, the total paid and the total interest. A text menu; the loan lives while the program runs.
main.eml- the menu and its questions, with their checks, and what the screen showsloan.eml- the exact payment, the rounding rules, the schedule and the yearly totalstext.eml- trimming, whole numbers, decimals read digit by digit, money and rates
Everything is whole numbers. Money is cents and the yearly rate is thousandths of a percent (5.25% is 5250), so the monthly rate is rate / 1,200,000. The standard payment, amount x r x (1 + r)^n / ((1 + r)^n - 1), then becomes amount x rate x A^n / (1,200,000 x (A^n - 1,200,000^n)) with A = 1,200,000 + rate: whole numbers of thousands of digits for a 30-year loan, kept exact, and divided by long division (EML has no //, and / goes through a float that cannot hold them). The screen shows the exact payment to four decimals before any rounding.
The rounding rules, stated:
- the payment is rounded to the nearest cent (half up), or, if you choose, up to the next cent;
- each month's interest is the balance times the monthly rate, rounded half up to the cent;
- the last payment is whatever clears the balance, so the schedule ends at exactly 0.00 and the total of the principal parts is exactly the loan.
So the last payment differs from the others by a few cents, and the two rounding rules show both ways: 300,000 at 6% for 30 years has an exact payment of 1,798.6515...; rounded to the nearest cent it is 1,798.65 and the last payment is 1,800.09, rounded up it is 1,798.66 and the last payment is 1,790.23 - and the cent more each month saves 6.27 of interest.
What is checked: an amount is written like 250000 or 1500.50, more than 0 and at most 10,000,000.00; a yearly rate has at most three decimals and is at most 30% (0 is allowed: then the payment is the amount divided by the months); the term is 1 to 40 whole years; the rounding is 1 or 2; a year to show is one of the loan's. Anything else asks again; nothing cancels.
Sessions: sessions/basic.in takes 300,000 at 6% for 30 years - the first payment is 1,500.00 of interest and 298.65 of principal, and after five years 279,163.14 is still owed - shows it by year, the months of the first and the last year, then the same loan with the payment rounded up and its last year; sessions/bad-input.in asks for the summary and the tables before any loan, cancels, types amounts, rates, terms, roundings and years that are words, out of range or have too many decimals, then takes a loan at 0% whose last payment is 27.70 against 27.78.
Built on the verified corpus cases five-years-of-payments-bought-seven-percent-of-the-house (the same 300,000 loan: each payment first pays the interest on the whole balance) and money-in-cents (amounts kept in whole cents so the parts equal the whole).
Recorded sessions
What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.
bad-input
interpreter: byte-equal
== Loan calculator ==
1) new loan 2) summary 3) by year 4) months of a year 5) quit
choice> 6
Pick a number from 1 to 5.
== Loan calculator ==
1) new loan 2) summary 3) by year 4) months of a year 5) quit
choice> 2
No loan yet.
== Loan calculator ==
1) new loan 2) summary 3) by year 4) months of a year 5) quit
choice> 3
No loan yet.
== Loan calculator ==
1) new loan 2) summary 3) by year 4) months of a year 5) quit
choice> 4
No loan yet.
== Loan calculator ==
1) new loan 2) summary 3) by year 4) months of a year 5) quit
choice> 1
amount>
Cancelled.
== Loan calculator ==
1) new loan 2) summary 3) by year 4) months of a year 5) quit
choice> 1
amount> abc
Type an amount like 250000 or 1500.50, or nothing to cancel.
amount> 0
An amount is more than 0.
amount> 20000000
An amount is at most 10,000,000.00.
amount> 1000
yearly rate in %> seven
Type a yearly rate like 5.25 (at most three decimals), or nothing to cancel.
yearly rate in %> 5.1234
Type a yearly rate like 5.25 (at most three decimals), or nothing to cancel.
yearly rate in %> 31
The yearly rate is at most 30%.
yearly rate in %> 0
years> 0
Type a whole number of years from 1 to 40, or nothing to cancel.
years> 41
Type a whole number of years from 1 to 40, or nothing to cancel.
years> 3
payment rounding (1 = nearest cent, 2 = up to the next cent)> 3
Type 1 or 2, or nothing to cancel.
payment rounding (1 = nearest cent, 2 = up to the next cent)> 1
-- loan --
amount 1,000.00
yearly rate 0.000%
term 3 years
payments 36
exact payment 27.7777...
monthly payment 27.78 (to the nearest cent)
last payment 27.70 (payment 36 clears the balance)
total paid 1,000.00
total interest 0.00
== Loan calculator ==
1) new loan 2) summary 3) by year 4) months of a year 5) quit
choice> 4
year (1 to 3)> 0
Type a year from 1 to 3, or nothing to cancel.
year (1 to 3)> 4
Type a year from 1 to 3, or nothing to cancel.
year (1 to 3)> 3
-- year 3: payments 25 to 36 --
payment paid interest principal balance
25 27.78 0.00 27.78 305.50
26 27.78 0.00 27.78 277.72
27 27.78 0.00 27.78 249.94
28 27.78 0.00 27.78 222.16
29 27.78 0.00 27.78 194.38
30 27.78 0.00 27.78 166.60
31 27.78 0.00 27.78 138.82
32 27.78 0.00 27.78 111.04
33 27.78 0.00 27.78 83.26
34 27.78 0.00 27.78 55.48
35 27.78 0.00 27.78 27.70
36 27.70 0.00 27.70 0.00
== Loan calculator ==
1) new loan 2) summary 3) by year 4) months of a year 5) quit
choice> 2
-- loan --
amount 1,000.00
yearly rate 0.000%
term 3 years
payments 36
exact payment 27.7777...
monthly payment 27.78 (to the nearest cent)
last payment 27.70 (payment 36 clears the balance)
total paid 1,000.00
total interest 0.00
== Loan calculator ==
1) new loan 2) summary 3) by year 4) months of a year 5) quit
choice> 5
Bye.
What was typed (26 lines)
6
2
3
4
1
1
abc
0
20000000
1000
seven
5.1234
31
0
0
41
3
3
1
4
0
4
3
2
5
basic
interpreter: byte-equal
== Loan calculator ==
1) new loan 2) summary 3) by year 4) months of a year 5) quit
choice> 1
amount> 300000
yearly rate in %> 6
years> 30
payment rounding (1 = nearest cent, 2 = up to the next cent)> 1
-- loan --
amount 300,000.00
yearly rate 6.000%
term 30 years
payments 360
exact payment 1,798.6515...
monthly payment 1,798.65 (to the nearest cent)
last payment 1,800.09 (payment 360 clears the balance)
total paid 647,515.44
total interest 347,515.44
== Loan calculator ==
1) new loan 2) summary 3) by year 4) months of a year 5) quit
choice> 3
-- by year --
year interest principal balance
1 17,899.80 3,684.00 296,316.00
2 17,672.54 3,911.26 292,404.74
3 17,431.32 4,152.48 288,252.26
4 17,175.19 4,408.61 283,843.65
5 16,903.29 4,680.51 279,163.14
6 16,614.61 4,969.19 274,193.95
7 16,308.11 5,275.69 268,918.26
8 15,982.74 5,601.06 263,317.20
9 15,637.26 5,946.54 257,370.66
10 15,270.50 6,313.30 251,057.36
11 14,881.12 6,702.68 244,354.68
12 14,467.71 7,116.09 237,238.59
13 14,028.78 7,555.02 229,683.57
14 13,562.86 8,020.94 221,662.63
15 13,068.10 8,515.70 213,146.93
16 12,542.88 9,040.92 204,106.01
17 11,985.26 9,598.54 194,507.47
18 11,393.25 10,190.55 184,316.92
19 10,764.69 10,819.11 173,497.81
20 10,097.41 11,486.39 162,011.42
21 9,388.94 12,194.86 149,816.56
22 8,636.79 12,947.01 136,869.55
23 7,838.27 13,745.53 123,124.02
24 6,990.46 14,593.34 108,530.68
25 6,090.37 15,493.43 93,037.25
26 5,134.77 16,449.03 76,588.22
27 4,120.23 17,463.57 59,124.65
28 3,043.13 18,540.67 40,583.98
29 1,899.57 19,684.23 20,899.75
30 685.49 20,899.75 0.00
all 347,515.44 300,000.00
== Loan calculator ==
1) new loan 2) summary 3) by year 4) months of a year 5) quit
choice> 4
year (1 to 30)> 1
-- year 1: payments 1 to 12 --
payment paid interest principal balance
1 1,798.65 1,500.00 298.65 299,701.35
2 1,798.65 1,498.51 300.14 299,401.21
3 1,798.65 1,497.01 301.64 299,099.57
4 1,798.65 1,495.50 303.15 298,796.42
5 1,798.65 1,493.98 304.67 298,491.75
6 1,798.65 1,492.46 306.19 298,185.56
7 1,798.65 1,490.93 307.72 297,877.84
8 1,798.65 1,489.39 309.26 297,568.58
9 1,798.65 1,487.84 310.81 297,257.77
10 1,798.65 1,486.29 312.36 296,945.41
11 1,798.65 1,484.73 313.92 296,631.49
12 1,798.65 1,483.16 315.49 296,316.00
== Loan calculator ==
1) new loan 2) summary 3) by year 4) months of a year 5) quit
choice> 4
year (1 to 30)> 30
-- year 30: payments 349 to 360 --
payment paid interest principal balance
349 1,798.65 104.50 1,694.15 19,205.60
350 1,798.65 96.03 1,702.62 17,502.98
351 1,798.65 87.51 1,711.14 15,791.84
352 1,798.65 78.96 1,719.69 14,072.15
353 1,798.65 70.36 1,728.29 12,343.86
354 1,798.65 61.72 1,736.93 10,606.93
355 1,798.65 53.03 1,745.62 8,861.31
356 1,798.65 44.31 1,754.34 7,106.97
357 1,798.65 35.53 1,763.12 5,343.85
358 1,798.65 26.72 1,771.93 3,571.92
359 1,798.65 17.86 1,780.79 1,791.13
360 1,800.09 8.96 1,791.13 0.00
== Loan calculator ==
1) new loan 2) summary 3) by year 4) months of a year 5) quit
choice> 1
amount> 300000
yearly rate in %> 6
years> 30
payment rounding (1 = nearest cent, 2 = up to the next cent)> 2
-- loan --
amount 300,000.00
yearly rate 6.000%
term 30 years
payments 360
exact payment 1,798.6515...
monthly payment 1,798.66 (up to the next cent)
last payment 1,790.23 (payment 360 clears the balance)
total paid 647,509.17
total interest 347,509.17
== Loan calculator ==
1) new loan 2) summary 3) by year 4) months of a year 5) quit
choice> 4
year (1 to 30)> 30
-- year 30: payments 349 to 360 --
payment paid interest principal balance
349 1,798.66 104.45 1,694.21 19,196.36
350 1,798.66 95.98 1,702.68 17,493.68
351 1,798.66 87.47 1,711.19 15,782.49
352 1,798.66 78.91 1,719.75 14,062.74
353 1,798.66 70.31 1,728.35 12,334.39
354 1,798.66 61.67 1,736.99 10,597.40
355 1,798.66 52.99 1,745.67 8,851.73
356 1,798.66 44.26 1,754.40 7,097.33
357 1,798.66 35.49 1,763.17 5,334.16
358 1,798.66 26.67 1,771.99 3,562.17
359 1,798.66 17.81 1,780.85 1,781.32
360 1,790.23 8.91 1,781.32 0.00
== Loan calculator ==
1) new loan 2) summary 3) by year 4) months of a year 5) quit
choice> 5
Bye.
What was typed (18 lines)
1
300000
6
30
1
3
4
1
4
30
1
300000
6
30
2
4
30
5
Modules
The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.
main.eml(entry)
eml# P014 loan calculator: the monthly payment of a loan, its repayment
# schedule, the total paid and the total interest - worked out exactly, with
# the rounding rules stated: the payment is rounded to the nearest cent or up
# to the next cent, every month's interest is rounded half up to the cent, and
# the last payment clears the balance to exactly 0.00.
import loan
import text
def row(label, value):
return " " + ("%-18s" % label) + ("%16s" % value)
def ask_number(prompt, low, high, message):
# A whole number from low to high, asked again until one is typed; -1 if
# the answer is empty, which cancels.
while True:
text.trim(input(prompt)) => answer
if answer == "":
return 0 - 1
text.number(answer) => n
if n >= low and n <= high:
return n
message ^0
def ask_amount():
# An amount in cents, asked again until a good one is typed; -1 cancels.
while True:
text.trim(input("amount> ")) => answer
if answer == "":
return 0 - 1
text.decimal(answer, 2) => c
if c == 0 - 1:
"Type an amount like 250000 or 1500.50, or nothing to cancel." ^0
elif c == 0:
"An amount is more than 0." ^0
elif c > 1000000000:
"An amount is at most 10,000,000.00." ^0
else:
return c
def ask_rate():
# A yearly rate in thousandths of a percent; -1 cancels.
while True:
text.trim(input("yearly rate in %> ")) => answer
if answer == "":
return 0 - 1
text.decimal(answer, 3) => r
if r == 0 - 1:
"Type a yearly rate like 5.25 (at most three decimals), or nothing to cancel." ^0
elif r > 30000:
"The yearly rate is at most 30%." ^0
else:
return r
def show_summary(l):
# l is [amount, rate, years, rule, payment, rows, exact payment].
l[5] => rows
0 => total_paid
0 => total_interest
for r in rows:
total_paid + r[1] => total_paid
total_interest + r[2] => total_interest
l[6] => f
"" => dots
if (f[0] * 100) % f[1] != 0:
"..." => dots
"to the nearest cent" => how
if l[3] == "up":
"up to the next cent" => how
str(l[2]) + " years" => term
if l[2] == 1:
"1 year" => term
rows[len(rows) - 1] => last
"" ^0
"-- loan --" ^0
row("amount", text.money(l[0])) ^0
row("yearly rate", text.rate_text(l[1])) ^0
row("term", term) ^0
row("payments", str(len(rows))) ^0
row("exact payment", text.fixed(loan.quotient(f[0] * 100, f[1]), 4) + dots) ^0
(row("monthly payment", text.money(l[4])) + " (" + how + ")") ^0
(row("last payment", text.money(last[1])) + " (payment " + str(last[0]) + " clears the balance)") ^0
row("total paid", text.money(total_paid)) ^0
row("total interest", text.money(total_interest)) ^0
None => current
True => running
while running:
"" ^0
"== Loan calculator ==" ^0
"1) new loan 2) summary 3) by year 4) months of a year 5) quit" ^0
text.trim(input("choice> ")) => choice
if choice == "1":
ask_amount() => amount
0 - 1 => rate
0 - 1 => years
0 - 1 => rule
if amount != 0 - 1:
ask_rate() => rate
if rate != 0 - 1:
ask_number("years> ", 1, 40, "Type a whole number of years from 1 to 40, or nothing to cancel.") => years
if years != 0 - 1:
ask_number("payment rounding (1 = nearest cent, 2 = up to the next cent)> ", 1, 2, "Type 1 or 2, or nothing to cancel.") => rule
if rule == 0 - 1:
"Cancelled." ^0
else:
"nearest" => how
if rule == 2:
"up" => how
loan.exact_payment(amount, rate, 12 * years) => f
loan.rounded(f, how) => payment
loan.schedule(amount, rate, 12 * years, payment) => rows
[amount, rate, years, how, payment, rows, f] => current
show_summary(current)
elif choice == "2":
if current == None:
"No loan yet." ^0
else:
show_summary(current)
elif choice == "3":
if current == None:
"No loan yet." ^0
else:
"" ^0
"-- by year --" ^0
(" " + ("%4s" % "year") + ("%14s" % "interest") + ("%14s" % "principal") + ("%16s" % "balance")) ^0
0 => all_interest
0 => all_principal
for y in loan.by_year(current[5]):
(" " + ("%4d" % y[0]) + ("%14s" % text.money(y[1])) + ("%14s" % text.money(y[2])) + ("%16s" % text.money(y[3]))) ^0
all_interest + y[1] => all_interest
all_principal + y[2] => all_principal
(" " + ("%4s" % "all") + ("%14s" % text.money(all_interest)) + ("%14s" % text.money(all_principal))) ^0
elif choice == "4":
if current == None:
"No loan yet." ^0
else:
current[2] => last_year
ask_number("year (1 to " + str(last_year) + ")> ", 1, last_year, "Type a year from 1 to " + str(last_year) + ", or nothing to cancel.") => y
if y == 0 - 1:
"Cancelled." ^0
else:
"" ^0
("-- year " + str(y) + ": payments " + str(12 * y - 11) + " to " + str(12 * y) + " --") ^0
(" " + ("%7s" % "payment") + ("%12s" % "paid") + ("%12s" % "interest") + ("%13s" % "principal") + ("%16s" % "balance")) ^0
for r in current[5]:
if r[0] > 12 * y - 12 and r[0] <= 12 * y:
(" " + ("%7d" % r[0]) + ("%12s" % text.money(r[1])) + ("%12s" % text.money(r[2])) + ("%13s" % text.money(r[3])) + ("%16s" % text.money(r[4]))) ^0
elif choice == "5":
False => running
else:
"Pick a number from 1 to 5." ^0
"Bye." ^0
Python projection (main.py)
import loan
import text
def row(label, value):
return " " + "%-18s" % label + "%16s" % value
def ask_number(prompt, low, high, message):
while True:
answer = text.trim(input(prompt))
if answer == "":
return 0 - 1
n = text.number(answer)
if n >= low and n <= high:
return n
print(message)
def ask_amount():
while True:
answer = text.trim(input("amount> "))
if answer == "":
return 0 - 1
c = text.decimal(answer, 2)
if c == 0 - 1:
print("Type an amount like 250000 or 1500.50, or nothing to cancel.")
elif c == 0:
print("An amount is more than 0.")
elif c > 1000000000:
print("An amount is at most 10,000,000.00.")
else:
return c
def ask_rate():
while True:
answer = text.trim(input("yearly rate in %> "))
if answer == "":
return 0 - 1
r = text.decimal(answer, 3)
if r == 0 - 1:
print("Type a yearly rate like 5.25 (at most three decimals), or nothing to cancel.")
elif r > 30000:
print("The yearly rate is at most 30%.")
else:
return r
def show_summary(l):
rows = l[5]
total_paid = 0
total_interest = 0
for r in rows:
total_paid = total_paid + r[1]
total_interest = total_interest + r[2]
f = l[6]
dots = ""
if f[0] * 100 % f[1] != 0:
dots = "..."
how = "to the nearest cent"
if l[3] == "up":
how = "up to the next cent"
term = str(l[2]) + " years"
if l[2] == 1:
term = "1 year"
last = rows[len(rows) - 1]
print("")
print("-- loan --")
print(row("amount", text.money(l[0])))
print(row("yearly rate", text.rate_text(l[1])))
print(row("term", term))
print(row("payments", str(len(rows))))
print(row("exact payment", text.fixed(loan.quotient(f[0] * 100, f[1]), 4) + dots))
print(row("monthly payment", text.money(l[4])) + " (" + how + ")")
print(row("last payment", text.money(last[1])) + " (payment " + str(last[0]) + " clears the balance)")
print(row("total paid", text.money(total_paid)))
print(row("total interest", text.money(total_interest)))
current = None
running = True
while running:
print("")
print("== Loan calculator ==")
print("1) new loan 2) summary 3) by year 4) months of a year 5) quit")
choice = text.trim(input("choice> "))
if choice == "1":
amount = ask_amount()
rate = 0 - 1
years = 0 - 1
rule = 0 - 1
if amount != 0 - 1:
rate = ask_rate()
if rate != 0 - 1:
years = ask_number("years> ", 1, 40, "Type a whole number of years from 1 to 40, or nothing to cancel.")
if years != 0 - 1:
rule = ask_number("payment rounding (1 = nearest cent, 2 = up to the next cent)> ", 1, 2, "Type 1 or 2, or nothing to cancel.")
if rule == 0 - 1:
print("Cancelled.")
else:
how = "nearest"
if rule == 2:
how = "up"
f = loan.exact_payment(amount, rate, 12 * years)
payment = loan.rounded(f, how)
rows = loan.schedule(amount, rate, 12 * years, payment)
current = [amount, rate, years, how, payment, rows, f]
show_summary(current)
elif choice == "2":
if current == None:
print("No loan yet.")
else:
show_summary(current)
elif choice == "3":
if current == None:
print("No loan yet.")
else:
print("")
print("-- by year --")
print(" " + "%4s" % "year" + "%14s" % "interest" + "%14s" % "principal" + "%16s" % "balance")
all_interest = 0
all_principal = 0
for y in loan.by_year(current[5]):
print(" " + "%4d" % y[0] + "%14s" % text.money(y[1]) + "%14s" % text.money(y[2]) + "%16s" % text.money(y[3]))
all_interest = all_interest + y[1]
all_principal = all_principal + y[2]
print(" " + "%4s" % "all" + "%14s" % text.money(all_interest) + "%14s" % text.money(all_principal))
elif choice == "4":
if current == None:
print("No loan yet.")
else:
last_year = current[2]
y = ask_number("year (1 to " + str(last_year) + ")> ", 1, last_year, "Type a year from 1 to " + str(last_year) + ", or nothing to cancel.")
if y == 0 - 1:
print("Cancelled.")
else:
print("")
print("-- year " + str(y) + ": payments " + str(12 * y - 11) + " to " + str(12 * y) + " --")
print(" " + "%7s" % "payment" + "%12s" % "paid" + "%12s" % "interest" + "%13s" % "principal" + "%16s" % "balance")
for r in current[5]:
if r[0] > 12 * y - 12 and r[0] <= 12 * y:
print(" " + "%7d" % r[0] + "%12s" % text.money(r[1]) + "%12s" % text.money(r[2]) + "%13s" % text.money(r[3]) + "%16s" % text.money(r[4]))
elif choice == "5":
running = False
else:
print("Pick a number from 1 to 5.")
print("Bye.")
loan.eml
eml# P014 loan calculator - the monthly payment and the schedule, worked out
# exactly. Money is whole cents, and the yearly rate is kept in thousandths of
# a percent (5.25% is 5250), so the monthly rate is R / 1,200,000 and no float
# is involved anywhere.
1200000 => B
def quotient(a, b):
# a // b for whole numbers a >= 0 and b > 0, exact at any size. Long
# division by doubling: take away the largest b * 2^k that still fits.
0 => q
while a >= b:
b => m
1 => k
while m + m <= a:
m + m => m
k + k => k
a - m => a
q + k => q
return q
def exact_payment(amount, rate, months):
# The exact monthly payment in cents, as a fraction [top, bottom]. With
# r = rate / B it is amount * r * (1 + r)^months / ((1 + r)^months - 1),
# which is amount * rate * A^months / (B * (A^months - B^months)) for
# A = B + rate - whole numbers with thousands of digits, kept exact.
# With no interest it is amount / months.
if rate == 0:
return [amount, months]
B + rate => A
1 => a_pow
1 => b_pow
for i in [1:months]:
a_pow * A => a_pow
b_pow * B => b_pow
return [amount * rate * a_pow, B * (a_pow - b_pow)]
def rounded(f, rule):
# The fraction f of cents as whole cents: rule "nearest" rounds half up,
# rule "up" rounds any part of a cent up.
if rule == "up":
return quotient(f[0] + f[1] - 1, f[1])
return quotient(2 * f[0] + f[1], 2 * f[1])
def half_up(n, d):
# n / d rounded half up, for whole numbers n >= 0 and d > 0 that stay
# below 2^53, where the float division of an exact multiple is exact.
2 * n + d => t
return int((t - t % (2 * d)) / (2 * d))
def schedule(amount, rate, months, payment):
# One row [number, paid, interest, principal, balance after] per month.
# Each month's interest is the balance times the monthly rate, rounded
# half up to the cent; the last payment is whatever clears the balance.
[] => rows
amount => balance
for k in [1:months]:
half_up(balance * rate, B) => interest
payment => paid
if k == months or paid > balance + interest:
balance + interest => paid
paid - interest => principal
balance - principal => balance
rows + [[k, paid, interest, principal, balance]] => rows
if balance == 0:
return rows
return rows
def by_year(rows):
# One row [year, interest, principal, balance at the end] per year.
[] => years
for r in rows:
int((r[0] - 1 - (r[0] - 1) % 12) / 12) + 1 => y
if len(years) < y:
years + [[y, 0, 0, 0]] => years
years[y - 1][1] + r[2] => years[y - 1][1]
years[y - 1][2] + r[3] => years[y - 1][2]
r[4] => years[y - 1][3]
return years
Python projection (loan.py)
B = 1200000
def quotient(a, b):
q = 0
while a >= b:
m = b
k = 1
while m + m <= a:
m = m + m
k = k + k
a = a - m
q = q + k
return q
def exact_payment(amount, rate, months):
if rate == 0:
return [amount, months]
A = B + rate
a_pow = 1
b_pow = 1
for i in range(1, months+1):
a_pow = a_pow * A
b_pow = b_pow * B
return [amount * rate * a_pow, B * (a_pow - b_pow)]
def rounded(f, rule):
if rule == "up":
return quotient(f[0] + f[1] - 1, f[1])
return quotient(2 * f[0] + f[1], 2 * f[1])
def half_up(n, d):
t = 2 * n + d
return int((t - t % (2 * d)) / (2 * d))
def schedule(amount, rate, months, payment):
rows = []
balance = amount
for k in range(1, months+1):
interest = half_up(balance * rate, B)
paid = payment
if k == months or paid > balance + interest:
paid = balance + interest
principal = paid - interest
balance = balance - principal
rows = rows + [[k, paid, interest, principal, balance]]
if balance == 0:
return rows
return rows
def by_year(rows):
years = []
for r in rows:
y = int((r[0] - 1 - (r[0] - 1) % 12) / 12) + 1
if len(years) < y:
years = years + [[y, 0, 0, 0]]
years[y - 1][1] = years[y - 1][1] + r[2]
years[y - 1][2] = years[y - 1][2] + r[3]
years[y - 1][3] = r[4]
return years
text.eml
eml# P014 loan calculator - reading what is typed and writing money and rates.
# Amounts are whole cents and rates thousandths of a percent from the moment
# they are typed: "5.25" is read digit by digit into 5250 and never passes
# through a float. The interpreter that checks every session does not run
# string methods yet, so the text handling is written out here.
def trim(s):
# s without the spaces at either end.
0 => i
len(s) => j
while i < j and s[i] == " ":
i + 1 => i
while j > i and s[j - 1] == " ":
j - 1 => j
return s[i:j]
def number(s):
# The value of s if it is digits only (at least one), otherwise -1.
if s == "":
return 0 - 1
0 => n
for c in s:
if not (c in "0123456789"):
return 0 - 1
n * 10 + int(c) => n
return n
def decimal(s, places):
# The number in s times 10^places, or -1 if s is not digits followed by a
# point and 1 to places digits, if any.
0 => whole
0 => whole_digits
0 => frac
0 => frac_digits
False => point
for c in s:
if c == ".":
if point:
return 0 - 1
True => point
elif c in "0123456789":
if point:
frac * 10 + int(c) => frac
frac_digits + 1 => frac_digits
else:
whole * 10 + int(c) => whole
whole_digits + 1 => whole_digits
else:
return 0 - 1
if whole_digits == 0:
return 0 - 1
if point and (frac_digits == 0 or frac_digits > places):
return 0 - 1
while frac_digits < places:
frac * 10 => frac
frac_digits + 1 => frac_digits
while places > 0:
whole * 10 => whole
places - 1 => places
return whole + frac
def grouped(digits):
# "1234567" as "1,234,567".
"" => out
len(digits) => i
while i > 3:
"," + digits[i - 3:i] + out => out
i - 3 => i
return digits[0:i] + out
def fixed(n, places):
# The whole number n >= 0 read as having places decimals: 179865 with 2
# places is 1,798.65.
str(n) => s
while len(s) < places + 1:
"0" + s => s
return grouped(s[0:len(s) - places]) + "." + s[len(s) - places:len(s)]
def money(c):
return fixed(c, 2)
def rate_text(r):
# A rate in thousandths of a percent as 5.250%.
return fixed(r, 3) + "%"
Python projection (text.py)
def trim(s):
i = 0
j = len(s)
while i < j and s[i] == " ":
i = i + 1
while j > i and s[j - 1] == " ":
j = j - 1
return s[i:j]
def number(s):
if s == "":
return 0 - 1
n = 0
for c in s:
if not c in "0123456789":
return 0 - 1
n = n * 10 + int(c)
return n
def decimal(s, places):
whole = 0
whole_digits = 0
frac = 0
frac_digits = 0
point = False
for c in s:
if c == ".":
if point:
return 0 - 1
point = True
elif c in "0123456789":
if point:
frac = frac * 10 + int(c)
frac_digits = frac_digits + 1
else:
whole = whole * 10 + int(c)
whole_digits = whole_digits + 1
else:
return 0 - 1
if whole_digits == 0:
return 0 - 1
if point and (frac_digits == 0 or frac_digits > places):
return 0 - 1
while frac_digits < places:
frac = frac * 10
frac_digits = frac_digits + 1
while places > 0:
whole = whole * 10
places = places - 1
return whole + frac
def grouped(digits):
out = ""
i = len(digits)
while i > 3:
out = "," + digits[i - 3:i] + out
i = i - 3
return digits[0:i] + out
def fixed(n, places):
s = str(n)
while len(s) < places + 1:
s = "0" + s
return grouped(s[0:len(s) - places]) + "." + s[len(s) - places:len(s)]
def money(c):
return fixed(c, 2)
def rate_text(r):
return fixed(r, 3) + "%"