Finance

CompoundCalc

Grows a balance year by year with contributions and interest apart, after inflation, and finds the rate needed to reach a target.

#smart#interactive#finance#savings#interest#calculator

Demo

Preview

Compound interest

Contribute at the
Balance
$144,572.72
Contributions
$48,000.00
Interest
$86,572.72
In today's money
$144,572.72
YearContributionsInterestBalanceIn today's money
1$2,400.00$801.42$13,201.42$13,201.42
2$2,400.00$1,032.85$16,634.27$16,634.27
3$2,400.00$1,281.01$20,315.28$20,315.28
4$2,400.00$1,547.11$24,262.39$24,262.39
5$2,400.00$1,832.44$28,494.83$28,494.83
6$2,400.00$2,138.41$33,033.24$33,033.24
7$2,400.00$2,466.50$37,899.74$37,899.74
8$2,400.00$2,818.29$43,118.03$43,118.03
9$2,400.00$3,195.52$48,713.55$48,713.55
10$2,400.00$3,600.03$54,713.58$54,713.58
11$2,400.00$4,033.76$61,147.34$61,147.34
12$2,400.00$4,498.86$68,046.20$68,046.20
13$2,400.00$4,997.59$75,443.79$75,443.79
14$2,400.00$5,532.35$83,376.14$83,376.14
15$2,400.00$6,105.79$91,881.93$91,881.93
16$2,400.00$6,720.67$101,002.60$101,002.60
17$2,400.00$7,380.00$110,782.60$110,782.60
18$2,400.00$8,087.00$121,269.60$121,269.60
19$2,400.00$8,845.10$132,514.70$132,514.70
20$2,400.00$9,658.02$144,572.72$144,572.72

Yearly deposits, daily interest

Contribute at the
Balance
€104,638.64
Contributions
€30,000.00
Interest
€69,638.64
In today's money
€49,885.71

Reaching €500,000.00 takes a rate of 12.02% a year.

YearContributionsInterestBalanceIn today's money
1€1,000.00€339.22€6,339.22€6,184.60
2€1,000.00€414.93€7,754.15€7,380.51
3€1,000.00€494.93€9,249.08€8,588.69
4€1,000.00€579.44€10,828.52€9,810.10
5€1,000.00€668.74€12,497.26€11,045.76
6€1,000.00€763.08€14,260.34€12,296.65
7€1,000.00€862.77€16,123.11€13,563.81
8€1,000.00€968.07€18,091.18€14,848.28
9€1,000.00€1,079.35€20,170.53€16,151.11
10€1,000.00€1,196.90€22,367.43€17,473.40
11€1,000.00€1,321.10€24,688.53€18,816.24
12€1,000.00€1,452.34€27,140.87€20,180.75
13€1,000.00€1,590.98€29,731.85€21,568.09
14€1,000.00€1,737.46€32,469.31€22,979.41
15€1,000.00€1,892.23€35,361.54€24,415.92
16€1,000.00€2,055.74€38,417.28€25,878.84
17€1,000.00€2,228.51€41,645.79€27,369.41
18€1,000.00€2,411.03€45,056.82€28,888.90
19€1,000.00€2,603.88€48,660.70€30,438.62
20€1,000.00€2,807.63€52,468.33€32,019.90
21€1,000.00€3,022.90€56,491.23€33,634.10
22€1,000.00€3,250.33€60,741.56€35,282.63
23€1,000.00€3,490.64€65,232.20€36,966.91
24€1,000.00€3,744.52€69,976.72€38,688.40
25€1,000.00€4,012.76€74,989.48€40,448.62
26€1,000.00€4,296.15€80,285.63€42,249.09
27€1,000.00€4,595.59€85,881.22€44,091.39
28€1,000.00€4,911.94€91,793.16€45,977.15
29€1,000.00€5,246.17€98,039.33€47,908.02
30€1,000.00€5,599.31€104,638.64€49,885.71

Change something to see on_change.

Get it

It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.

$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/compound_calc.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/compound_calc.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/compound_calc.qwe
component CompoundCalcDemoLive() {    state note = "Change something to see on_change."    <div.demo-stack>        <CompoundCalc id="compound-calc-demo-live" name="pension" title="Yearly deposits, daily interest" start="5000" contribution="1000" contribute_per_year={1} timing="start"                      rate={5.5} compound_per_year={365} years={30} inflation={2.5} target="500000" currency="EUR"                      on_change={(r) => { note = `on_change: balance ${r.balance}, interest ${r.interest}, needed ${r.needed_rate}.` }}/>        <p.demo-note>{note}</p>    </div>}view CompoundCalcDemo() =    <div.demo-stack>        <CompoundCalc id="compound-calc-demo" start="10000" contribution="200" rate={7} years={20}/>        <CompoundCalcDemoLive/>    </div>

Source

ui/compound_calc.qwe
// CompoundCalc: what savings grow to, year by year. A starting amount and regular// contributions, made at the start or the end of each period, at a yearly rate compounded// as often as the account says, with contributions and interest kept apart so that the// rows always add up to the balance to the cent, and each balance in today's money after// inflation. How often contributions are made and how often interest compounds can// differ: each contribution period grows by the equivalent rate, (1 + r/m)^(m/p), an// exact power and roots by Newton's method, all in decimals. With a target it says// the lowest yearly rate, to 0.01%, that reaches it, or that no rate up to 1000% does.////   <CompoundCalc start="10000" contribution="200" rate={7} years={20}/>//   <CompoundCalc start="5000" contribution="1000" contribute_per_year={1} timing="start" rate={5.5} compound_per_year={365}//                 years={30} inflation={2.5} target="500000" currency="EUR" on_change={(r) => { total = r.balance }}/>//// start, contribution, rate, years, inflation, target: where the fields start (numbers,// decimals or numeric text; empty counts as 0, target empty as none). rate and inflation are// yearly percents, up to 1000 and 100; years is a whole number from 1 to 100 (10).// contribute_per_year: 1, 2, 4 or 12 (12). compound_per_year: 1, 2, 4, 12 or 365 (12). timing:// "end" of the period (the default) or "start". currency: ISO code ("USD"; one Quillo does not// know falls back to it). locale: how to write amounts (the program's). title: the heading// ("Compound interest"). id: a prefix for its ids ("compound-calc"), unique on the page. name: a// prefix for the names of its fields ("compound": compound_start, compound_contribution,// compound_contribute_per_year, compound_timing, compound_rate, compound_compound_per_year,// compound_years, compound_inflation, compound_target). on_change(r): inside one of your// components, after each change, given { balance, contributed, interest, real, needed_rate }// as decimals when the figures are good (needed_rate null without an answer).// class: more classes for its outer element. A balance past 10 trillion is too large// to keep exact, so the table stops there and says so.//// compound_calc_schedule(start, contribution, per_year, at_start, rate, compound_per_year,// years, inflation) and compound_calc_needed(...) are the logic, for a program's own use.import ui/theme.qwe use ui_clslet compound_calc_css = css`.ui-compound-calc { container-type: inline-size; display: grid; gap: 16px; min-width: 0; max-width: 720px; padding: 20px 22px; border: 1px solid var(--ui-border);  border-radius: var(--ui-radius); background: var(--ui-surface); color: var(--ui-fg); font: 14px/1.45 var(--ui-font); }.ui-compound-calc .ui-compound-calc-title { margin: 0; font-size: 16px; font-weight: 650; }.ui-compound-calc-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px 14px; }.ui-compound-calc-field { display: grid; gap: 5px; min-width: 0; align-content: start; }.ui-compound-calc-field label { font-size: 13px; font-weight: 500; }.ui-compound-calc-field input, .ui-compound-calc-field select { box-sizing: border-box; width: 100%; min-width: 0; height: 36px; padding: 0 10px; border: 1px solid var(--ui-border);  border-radius: var(--ui-radius-sm); background: var(--ui-surface); color: var(--ui-fg); font: 14px var(--ui-font); font-variant-numeric: tabular-nums; }.ui-compound-calc-field input:hover, .ui-compound-calc-field select:hover { border-color: var(--ui-strong); }.ui-compound-calc-field input:focus, .ui-compound-calc-field select:focus { outline: none; border-color: var(--ui-accent); box-shadow: var(--ui-focus); }.ui-compound-calc-field input[aria-invalid] { border-color: var(--ui-danger); }.ui-compound-calc-timing { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 16px; margin: 0; padding: 0; border: 0; }.ui-compound-calc-timing legend { float: left; margin-right: 8px; padding: 0; font-size: 13px; font-weight: 500; }.ui-compound-calc-timing label { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }.ui-compound-calc-timing input { margin: 0; accent-color: var(--ui-accent); }.ui-compound-calc-errors { margin: 0; padding: 0; list-style: none; color: var(--ui-danger); font-size: 13px; }.ui-compound-calc-out { display: grid; gap: 12px; padding: 12px 16px; border-radius: var(--ui-radius-sm); background: var(--ui-subtle); }.ui-compound-calc-sums { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 8px 16px; margin: 0; }.ui-compound-calc-sums dt { color: var(--ui-muted); font-size: 12px; }.ui-compound-calc-sums dd { margin: 0; font-size: 16px; font-weight: 650; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }.ui-compound-calc-needed, .ui-compound-calc-note { margin: 0; font-size: 13px; }.ui-compound-calc-note { color: var(--ui-warning); }.ui-compound-calc-scroll { max-height: 320px; overflow: auto; }.ui-compound-calc-scroll:focus-visible { outline: 2px solid var(--ui-accent); outline-offset: 2px; }.ui-compound-calc-table { width: 100%; border-collapse: collapse; font-size: 13px; font-variant-numeric: tabular-nums; }.ui-compound-calc-table th { position: sticky; top: 0; padding: 4px 0; border-bottom: 1px solid var(--ui-border); background: var(--ui-subtle); color: var(--ui-muted); font-size: 12px; font-weight: 600; text-align: right; }.ui-compound-calc-table td { padding: 4px 0; border-bottom: 1px solid var(--ui-border); text-align: right; }.ui-compound-calc-table th + th, .ui-compound-calc-table td + td { padding-left: 10px; }.ui-compound-calc-table th:first-child, .ui-compound-calc-table td:first-child { text-align: left; }.ui-compound-calc-table td:nth-child(4) { font-weight: 600; }`// The currency's code, USD when Quillo does not know it.fn compound_calc_code(currency) -> str {    let c = upper(trim(str(currency ?? "")))    type_of(money.minor_units(c)) == "error" ? "USD" : c}// What was typed as a decimal: `blank` when empty, null when it is not a plain number from// 0 to most. A comma counts only as a thousands comma, so "12,50" is refused, not 1250.fn compound_calc_read(text, most, blank) {    let t = trim(str(text ?? ""))    if t == "" { return blank }    if len(t) > 40 { return null }    let plain = re.test(t, "^[0-9]{1,3}(,[0-9]{3})+(\\.[0-9]*)?$") ? replace(t, ",", "") : t    if !re.test(plain, "^([0-9]+\\.?[0-9]*|\\.[0-9]+)$") { return null }    let d = dec(plain)    is_err(d) || d > most ? null : d}// x to the power n, a whole n, by squaring. The rates allowed keep this well inside a decimal.fn compound_calc_pow(x: dec, n: int) -> dec {    let out = 1m    let base = x    let left = n    while left > 0 {        if left % 2 == 1 { out = out * base }        left = int(floor(left / 2))        if left > 0 { base = base * base }    }    out}fn compound_calc_gcd(a: int, b: int) -> int = b == 0 ? a : compound_calc_gcd(b, a % b)// The k-th root of a (a >= 1), for a k made of twos and threes, as periods a year are:// decimals have no fractional power, and Newton's method on a high power from a large// start runs out of digits, so a square or cube root at a time.fn compound_calc_root(a: dec, k: int) -> dec {    if k <= 1 { return a }    let n = k % 2 == 0 ? 2 : 3    let x = 1m + (a - 1m) / n    let i = 0    let moving = true    while moving && i < 200 {        let below = n == 2 ? x : x * x        let step = (below * x - a) / (below * n)        x = x - step        moving = abs(step) > 0.00000000000000000000000001m        i += 1    }    compound_calc_root(x, int(k / n))}// What one contribution period multiplies a balance by: (1 + r/m)^(m/p) for a yearly// percent r compounded m times a year, with p contribution periods a year.fn compound_calc_growth(rate: dec, compound_per_year: int, per_year: int) -> dec {    let a = 1m + rate / 100 / compound_per_year    let g = compound_calc_gcd(compound_per_year, per_year)    compound_calc_root(compound_calc_pow(a, int(compound_per_year / g)), int(per_year / g))}// The year-by-year schedule; amounts are decimals in, whole minor units of `code` out.// Gives { rows: [{ year, contributed, interest, balance, real }], stopped, balance, real,// contributed, interest, opening }; rows always add up: opening + contributed + interest = balance.fn compound_calc_schedule(start: dec, contribution: dec, per_year: int, at_start: bool, rate: dec, compound_per_year: int, years: int, inflation: dec, code: str) -> map {    let minor = (x: dec) => money.to_minor(money.of(x, code, "half_up"))    let g = compound_calc_growth(rate, compound_per_year, per_year)    let opening = minor(start)    let paid = minor(contribution) * per_year    let balance = start    let shown = opening    let deflator = 1m    let rows = []    let stopped = null    let y = 1    while y <= years && stopped == null {        let k = 0        while k < per_year {            balance = at_start ? (balance + contribution) * g : balance * g + contribution            k += 1        }        if balance > 10000000000000m {            stopped = y        } else {            deflator = deflator > 1000000000000000000000000m ? deflator : deflator * (1m + inflation / 100)            let now_shown = minor(balance)            rows = rows | push({ year: y, contributed: paid, interest: now_shown - shown - paid, balance: now_shown, real: minor(balance / deflator) })            shown = now_shown        }        y += 1    }    let end = last(rows)    { rows, stopped, opening, balance: shown, real: end == null ? opening : end.real, contributed: sum(rows | map((r) => r.contributed)), interest: sum(rows | map((r) => r.interest)) }}// True when a rate reaches the target by the end, stopping as soon as it does.fn compound_calc_reaches(rate: dec, start: dec, contribution: dec, per_year: int, at_start: bool, compound_per_year: int, years: int, target: dec) -> bool {    let g = compound_calc_growth(rate, compound_per_year, per_year)    let balance = start    let k = 0    let periods = per_year * years    let cap = target * 2 + 1m    while k < periods && balance < cap {        balance = at_start ? (balance + contribution) * g : balance * g + contribution        k += 1    }    balance >= target}// The lowest yearly percent, in steps of 0.01, that reaches target, as { rate } ; { rate: 0 }// when saving alone does; { rate: null } when not even 1000% does. Halves the range, about 17 runs.fn compound_calc_needed(start: dec, contribution: dec, per_year: int, at_start: bool, compound_per_year: int, years: int, target: dec) -> map {    let hits = (h: int) => compound_calc_reaches(dec(h) / 100, start, contribution, per_year, at_start, compound_per_year, years, target)    if hits(0) { return { rate: 0m } }    if !hits(100000) { return { rate: null } }    let low = 0    let high = 100000    while high - low > 1 {        let mid = int(floor((low + high) / 2))        if hits(mid) { high = mid } else { low = mid }    }    { rate: dec(high) / 100 }}// All the figures from the fields as typed: { errors, schedule, needed, target }; schedule is// null with errors.fn compound_calc_work(start_text, contribution_text, per_year: int, at_start: bool, rate_text, compound_per_year: int, years_text, inflation_text, target_text, code: str) -> map {    let errors = []    let most = 1000000000000m    let start = compound_calc_read(start_text, most, 0m)    let contribution = compound_calc_read(contribution_text, 1000000000m, 0m)    let rate = compound_calc_read(rate_text, 1000m, 0m)    let years = compound_calc_read(years_text, 100m, null)    let inflation = compound_calc_read(inflation_text, 100m, 0m)    let target = compound_calc_read(target_text, 10000000000000m, null)    let whole_years = years != null && years == floor(years) && years >= 1    if start == null { errors = errors | push({ field: "start", text: "The starting amount must be a number of 0 or more, up to 1,000,000,000,000." }) }    if contribution == null { errors = errors | push({ field: "contribution", text: "The contribution must be a number of 0 or more, up to 1,000,000,000." }) }    if rate == null { errors = errors | push({ field: "rate", text: "The rate must be a percent from 0 to 1000." }) }    if !whole_years { errors = errors | push({ field: "years", text: "The years must be a whole number from 1 to 100." }) }    if inflation == null { errors = errors | push({ field: "inflation", text: "The inflation must be a percent from 0 to 100." }) }    if target == null && trim(str(target_text ?? "")) != "" { errors = errors | push({ field: "target", text: "The target must be a number of 0 or more, up to 10,000,000,000,000." }) }    if len(errors) > 0 { return { errors, schedule: null, needed: null, target: null } }    let schedule = compound_calc_schedule(start, contribution, per_year, at_start, rate, compound_per_year, int(years), inflation, code)    let needed = target != null && target > 0m ? compound_calc_needed(start, contribution, per_year, at_start, compound_per_year, int(years), target) : null    { errors, schedule, needed, target: target != null && target > 0m ? money.to_minor(money.of(target, code, "half_up")) : null }}component CompoundCalc(start = null, contribution = null, contribute_per_year: int = 12, timing: str = "end", rate = null, compound_per_year: int = 12, years = 10,                       inflation = null, target = null, currency: str = "USD", locale: str? = null, title: str = "Compound interest", id: str = "compound-calc",                       name: str = "compound", on_change = null, class: str? = null) {    style global compound_calc_css    state start_text = start == null ? "" : str(start)    state contribution_text = contribution == null ? "" : str(contribution)    state rate_text = rate == null ? "" : str(rate)    state years_text = years == null ? "" : str(years)    state inflation_text = inflation == null ? "" : str(inflation)    state target_text = target == null ? "" : str(target)    state per_year = contains([1, 2, 4, 12], contribute_per_year) ? contribute_per_year : 12    state compounding = contains([1, 2, 4, 12, 365], compound_per_year) ? compound_per_year : 12    state at_start = timing == "start"    let code = compound_calc_code(currency)    let work = compound_calc_work(start_text, contribution_text, per_year, at_start, rate_text, compounding, years_text, inflation_text, target_text, code)    let ok = work.schedule != null    let shown = (minor: int) => locale != null ? money.format(money.from_minor(minor, code), locale) : money.format(money.from_minor(minor, code))    let bad = (field: str) => work.errors | any((e) => e.field == field)    let field_id = (part: str) => `${id}-${part}`    let named = (part: str) => `${name}_${part}`    let tell = () => {        if on_change != null && ok {            let s = work.schedule            let amount = (minor: int) => money.from_minor(minor, code).amount            on_change({ balance: amount(s.balance), contributed: amount(s.contributed), interest: amount(s.interest), real: amount(s.real), needed_rate: work.needed == null ? null : work.needed.rate })        }    }    <section class={ui_cls(["ui-compound-calc", class])} aria-labelledby={field_id("title")}>        <h3.ui-compound-calc-title id={field_id("title")}>{title}</h3>        <div.ui-compound-calc-grid>            <div.ui-compound-calc-field>                <label for={field_id("start")}>Starting amount ({code})</label>                <input type="text" inputmode="decimal" autocomplete="off" id={field_id("start")} name={named("start")} value={start_text} placeholder="0.00" aria-invalid={bad("start") ? "true" : null}                       @input={(e) => { start_text = e.value; tell() }}/>            </div>            <div.ui-compound-calc-field>                <label for={field_id("contribution")}>Each contribution ({code})</label>                <input type="text" inputmode="decimal" autocomplete="off" id={field_id("contribution")} name={named("contribution")} value={contribution_text} placeholder="0.00" aria-invalid={bad("contribution") ? "true" : null}                       @input={(e) => { contribution_text = e.value; tell() }}/>            </div>            <div.ui-compound-calc-field>                <label for={field_id("per-year")}>Contributions</label>                <select id={field_id("per-year")} name={named("contribute_per_year")} @change={(e) => { per_year = int(e.value); tell() }}>                    <option value="12" selected={per_year == 12}>Monthly</option>                    <option value="4" selected={per_year == 4}>Quarterly</option>                    <option value="2" selected={per_year == 2}>Twice a year</option>                    <option value="1" selected={per_year == 1}>Yearly</option>                </select>            </div>            <div.ui-compound-calc-field>                <label for={field_id("rate")}>Rate a year (%)</label>                <input type="text" inputmode="decimal" autocomplete="off" id={field_id("rate")} name={named("rate")} value={rate_text} placeholder="0" aria-invalid={bad("rate") ? "true" : null}                       @input={(e) => { rate_text = e.value; tell() }}/>            </div>            <div.ui-compound-calc-field>                <label for={field_id("compounding")}>Compounding</label>                <select id={field_id("compounding")} name={named("compound_per_year")} @change={(e) => { compounding = int(e.value); tell() }}>                    <option value="365" selected={compounding == 365}>Daily</option>                    <option value="12" selected={compounding == 12}>Monthly</option>                    <option value="4" selected={compounding == 4}>Quarterly</option>                    <option value="2" selected={compounding == 2}>Twice a year</option>                    <option value="1" selected={compounding == 1}>Yearly</option>                </select>            </div>            <div.ui-compound-calc-field>                <label for={field_id("years")}>Years</label>                <input type="text" inputmode="numeric" autocomplete="off" id={field_id("years")} name={named("years")} value={years_text} aria-invalid={bad("years") ? "true" : null}                       @input={(e) => { years_text = e.value; tell() }}/>            </div>            <div.ui-compound-calc-field>                <label for={field_id("inflation")}>Inflation a year (%)</label>                <input type="text" inputmode="decimal" autocomplete="off" id={field_id("inflation")} name={named("inflation")} value={inflation_text} placeholder="0" aria-invalid={bad("inflation") ? "true" : null}                       @input={(e) => { inflation_text = e.value; tell() }}/>            </div>            <div.ui-compound-calc-field>                <label for={field_id("target")}>Target ({code}, optional)</label>                <input type="text" inputmode="decimal" autocomplete="off" id={field_id("target")} name={named("target")} value={target_text} aria-invalid={bad("target") ? "true" : null}                       @input={(e) => { target_text = e.value; tell() }}/>            </div>        </div>        <fieldset.ui-compound-calc-timing>            <legend>Contribute at the</legend>            <label><input type="radio" name={named("timing")} value="start" checked={at_start} @change={() => { at_start = true; tell() }}/> Start of each period</label>            <label><input type="radio" name={named("timing")} value="end" checked={!at_start} @change={() => { at_start = false; tell() }}/> End of each period</label>        </fieldset>        {len(work.errors) > 0 && <ul.ui-compound-calc-errors role="alert">{for e in work.errors => <li key={e.field}>{e.text}</li>}</ul>}        {ok && <div.ui-compound-calc-out aria-live="polite">            <dl.ui-compound-calc-sums>                <div><dt>Balance</dt><dd data-key="balance">{shown(work.schedule.balance)}</dd></div>                <div><dt>Contributions</dt><dd data-key="contributed">{shown(work.schedule.contributed)}</dd></div>                <div><dt>Interest</dt><dd data-key="interest">{shown(work.schedule.interest)}</dd></div>                <div><dt>In today's money</dt><dd data-key="real">{shown(work.schedule.real)}</dd></div>            </dl>            {work.schedule.stopped != null && <p.ui-compound-calc-note>The balance passes 10 trillion in year {work.schedule.stopped}, too large to keep exact, so the table stops before it.</p>}            {work.needed != null && <p.ui-compound-calc-needed data-key="needed">                {work.needed.rate == null ? `No rate up to 1000% reaches ${shown(work.target)}.`                    : (work.needed.rate == 0m ? `The contributions alone reach ${shown(work.target)}, with no interest.`                    : `Reaching ${shown(work.target)} takes a rate of ${work.needed.rate}% a year.`)}            </p>}            <div.ui-compound-calc-scroll tabindex="0" role="region" aria-label="Balance by year">                <table.ui-compound-calc-table>                    <thead><tr><th scope="col">Year</th><th scope="col">Contributions</th><th scope="col">Interest</th><th scope="col">Balance</th><th scope="col">In today's money</th></tr></thead>                    <tbody>                        {for r in work.schedule.rows => <tr key={r.year}><td>{r.year}</td><td>{shown(r.contributed)}</td><td>{shown(r.interest)}</td><td>{shown(r.balance)}</td><td>{shown(r.real)}</td></tr>}                    </tbody>                </table>            </div>        </div>}    </section>}
Thread
48Useful? Like it, or add to its thread below.

Thread 0 posts

On CompoundCalc: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.