DEV Community

Devanshu Biswas
Devanshu Biswas

Posted on

Your Withdrawal Rate Moves Time-to-FI by 31 Months. Your Balance, the Number in the Biggest Font, Moves It 3.

A freedom calculator answers three questions — how long the money lasts, what number ends the job, how far away that is — and they are one recurrence asked from two sides. Build it exactly, measure it honestly, and the finding is not that the arithmetic is wrong. It is that the answer is mostly a statement about assumptions, and the ones that move it most are the ones the genre does not show you.

Weekend Builds Vol 4 · #05 · the finale — Vol 4 is now complete at 5 of 5.

👉 Live, runs in your browser: https://dev48v.infy.uk/agentlab/vol4-05-freedom-calculator.html
👉 PUBLIC, MIT, dependencies = []: https://github.com/dev48v/freedom-calculator

python -m freedom_calculator                # runway, the FI number, time to FI
python -m freedom_calculator --sensitivity  # which input the answer rests on
python -m freedom_calculator --assumptions  # the model, and what it costs
python -m freedom_calculator --float        # why money is not a float here
Enter fullscreen mode Exit fullscreen mode

Declared inputs, derived signs, measured spans

You declare five numbers. The scenario on the page: 42,000 balance, 3,200 a month out, 5,400 a month in, 5% real return, 4% withdrawal rate. From those: an FI number of 960,000 (25× annual spend) and 231 months to reach it.

Every direction below is derived on paper from the recurrence, then measured by bumping the input and watching the answer. The two are kept apart so they can be checked against each other, and the suite asserts they agree for every input at six bump sizes — plus a non-vacuity test, because a calculator that ignored its inputs entirely would pass a sign check perfectly.

±10% on… months, down / base / up span
what you spend a month 198 / 231 / 269 71
what you earn a month 271 / 231 / 202 69
withdrawal rate (usually hidden) 248 / 231 / 217 31
real return you assume 241 / 231 / 222 19
balance you already have 233 / 231 / 230 3

The row to sit with is the last one. The genre is arranged around a number worth about a tenth of what the hidden constant is worth.

There is also a row that must be exactly zero: runway does not depend on the withdrawal rate at all. It is a drawdown question. The derived sign is 0 and the measured direction must be 0 too — not small, not usually, exactly, at every bump size on every scenario. That identity is what stops the two halves of the calculator leaking into each other.

The argument people actually have, priced

The 4% rule is a rule about a number under argument — 3% for the cautious, 5% for the cheerful:

withdrawal rate FI number months
3% 1,280,000 278
4% 960,000 231
5% 768,000 198

512,000 of spread — 53.3% of the headline number — and 80 months of your life, from one constant most calculators will not let you type.

Forgetting to subtract inflation is worth 43 months: a nominal 8% instead of a real 5% brings the date from 231 months forward to 188, on which date you actually hold ~717,562 against a target of 960,000.

Money is an exact rational because the output is an integer

Not fastidiousness. The output is a month count that turns on a comparison, and a comparison decided by the last bit of a binary approximation is a month count decided by the last bit of a binary approximation.

Over 120,000 balances that are exactly n months of spend, floor(balance / spend) in doubles answers n−1 in 12,736 of them — 10.61%. Exact rationals: 0. Whole months, not cents.

The zero-net-burn case is worse. Summed as floats, the same basket of line items lands on a net burn of 2.27e-13 instead of zero, and a calculator that divides reports 43,980,465,111,040,000 months with a straight face. This package reaches the unbounded answer with a comparison and no division at all:

// runway and time-to-FI are the SAME recurrence, asked from two sides
//     b[m+1] = b[m] * (1 + r) + flow          flow = income - spend
// the zero-net-burn identity, decided BEFORE any division ever happens
if (cmp(add(mul(balance, r), flow), ZERO) >= 0) return { status: "unbounded" };
Enter fullscreen mode Exit fullscreen mode

A test walks the syntax tree of runway() and asserts there is no division operator in it, with docstrings stripped first so it cannot pass on the paragraph explaining that the division is not there.

Same returns, reversed, a 17× different answer

The model has no sequence-of-returns risk in it. That is a limitation, and here is what it costs — with no RNG anywhere. One declared list of 30 yearly real returns, mean exactly the same 5% the smooth model uses, applied forward, then reversed. Start at the FI number, draw 4%:

order ending balance
forward (crash at the front) ~127,381
reversed ~2,163,974
the smooth model's answer ~1,625,807

17.0× from order alone. Same numbers, same mean, same multiset, same product — and the control proves the product is identical: withdraw nothing and the two orders end on the identical cent, 4,176,967.52, because multiplication commutes. The entire gap is order, and the smooth model's answer sits between the two giving no hint which one you got.

The sequence was built to put the crash at the front. Stated, not hidden — the size of the crash is not the point. The point is that reversing a list changes the answer at all, which the smooth model says is impossible.

What this is not

Deterministic arithmetic on declared inputs. No RNG, no Monte Carlo, no historical data. Constant real returns, constant burn in today's money, no taxes, no lumpy years, no property, no state pension. The monthly rate is the annual rate over twelve rather than its twelfth root — the usual convention, and slightly generous. Anything past a 100-year horizon is reported as beyond the horizon rather than as a number, because a model that answers "3,847 months" is not being more precise, it is being less honest.

The JavaScript on the page is exact rationals over BigInt, checked against the Python's reference.json numerator for denominator, and against a third implementation in the verifier that simulates month by month instead of using the closed form.

50 pytest · 239 verifier asserts · 44 in-page self-checks · 0 failures. MIT, Python standard library only.

Top comments (0)