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