A compound-interest calculator is one formula and about forty lines of JavaScript. The formula isn't the hard part. The hard part is that money has cents, and your floats don't.
I built a compound-interest calculator as a static page — no backend, everything in the browser. The core is a single line of math. But shipping it meant answering a question I hadn't expected to care about: how do you keep 360 months of floating-point arithmetic from lying about somebody's retirement?
The one line
Future value of a monthly contribution is the standard annuity formula:
FV = PMT × (((1 + r)ⁿ − 1) / r)
PMT is the monthly payment, r is the monthly rate (annual ÷ 12), n is the number of months. That's it. The calculator takes $500/month, 7% annual, 30 years, and evaluates this in the browser. Simple — until you check the cents.
Worked example: $500 a month for 30 years
Inputs: PMT = $500, r = 0.07/12 ≈ 0.0058333, n = 360.
- Future value: $609,985.50
- Total contributed: $180,000
- Interest earned: $429,985.50 — more than double what was put in
Now the figure that surprises everyone: run the same calculation for 20 years instead of 30. Future value: $260,463.33. So waiting ten years to start — contributing $120,000 instead of $180,000 — costs $349,522 of the final balance. The extra decade runs on only $60,000 of additional contributions and produces $349,522 of additional value. Compounding doesn't reward starting big. It rewards starting early, and the reward curve is steeper than intuition allows.
(Also worth knowing: 7% "nominal" compounded monthly is actually 7.229% effective annual — (1 + 0.07/12)¹² − 1. If a product quotes you a nominal rate, this conversion is where the small print lives.)
The float traps I actually hit
Trap 1: display. Every JS developer knows 0.1 + 0.2 === 0.30000000000000004. In a calculator, this surfaces as $28.999999999999996 where a user expects $29.00. The fix is old: compute in integer cents, or round exactly once at display time. Never show a raw float as money.
Trap 2: toFixed rounds in ways that surprise people. Try these in your console:
(0.615).toFixed(2) // "0.61" — not "0.62"
(1.005).toFixed(2) // "1.00" — not "1.01"
(2.675).toFixed(2) // "2.67"
This isn't a bug in toFixed. 0.615 in binary floating point is actually 0.61499999999999999111…, so rounding "down" is correct for the number you really have. The bug is in the programmer's mental model: the decimal you typed is not the number you're holding. If your calculator rounds intermediate values with toFixed and feeds the results back into more math, you're compounding representation error on top of interest.
Trap 3: the one I expected to be terrible, and wasn't. I assumed that accumulating month by month in float64 for 360 iterations would drift meaningfully from the closed-form answer. I tested it: the loop bal = bal * (1 + r) + 500 ends at 609985.4978879701; the exact value is 609985.4978879664…. The drift is less than a millionth of a cent. float64 accumulation over a human lifetime is essentially exact. The enemy was never the loop — it's display rounding and naive equality checks (balance === target after 360 float operations is a coin flip; compare with a tolerance, or better, in cents).
Trap 4: your calculator and the bank can both be right and disagree. A bank ledger rounds every transaction to cents. If you model that — round the balance to integer cents each month — the 30-year result is $609,985.57, seven cents above the continuous formula's $609,985.50. Seven cents on six hundred thousand dollars: irrelevant to the user, but if your test suite asserts exact equality against a spreadsheet, this is the kind of thing that makes a test red at 2 a.m. Decide what your source of truth is (I used the closed form, rounded once for display) and document it.
The rules I shipped with
- Do money math in integer cents. Multiply inputs by 100 at the boundary, divide by 100 at display. All intermediate arithmetic stays exact for any realistic amount.
- Round once, at the edge. Intermediate rounding compounds; display rounding doesn't.
- Never compare floats for equality. If you must compare, use an epsilon — or compare integer cents.
-
Don't trust
toFixedfor financial rounding unless you've verified the exact binary values involved. For display it's fine; for logic, cents are fine-er.
Honest caveats
- 7% is illustrative, not a promise. Historical market averages are not a forecast; inflation, taxes, and fees all take their cut, and this calculator models none of them.
- Nominal vs. effective matters. Always check which one a product quotes; the 7% → 7.229% conversion above is free money for whoever does the quoting.
- The formula assumes clockwork. Real contributions are irregular, rates vary, life happens. The closed form is a planning tool, not a prophecy.
Run your own numbers
Change the contribution, the rate, the timeline — and watch how violently the "start ten years earlier" column moves compared to everything else:
Compound interest calculator — free, no signup, runs entirely in your browser.
Your floats are fine. It's your rounding you should be afraid of.
Top comments (0)