In short
- More than thirty test files pin worked examples and check the structure of every page.
- 16 Python scripts sit beside them. 10 of them re-do the arithmetic from scratch and compare it with the page's own code. None disagreed at the last full run, on October 11, 2026. The other 6 are older worked examples that we do not count as checks.
- We planted 37 bugs on purpose, one at a time. The checks caught 37 of 37.
- Rates and rules that come from outside, such as Freddie Mac averages, are read from the source and dated on the page. We do not recompute them.
Tests
Each calculator's engine has unit tests that pin worked examples, worked out by hand or taken from a textbook: a 30-year loan of $200,000 at 6% has a payment of $1,199.10. Further tests read every page: one h1, a canonical address, structured data that matches the visible questions and answers word for word, links that resolve, outbound links only to named source domains, and an update date that agrees with the page's markup. They fail if a page loads a script or stylesheet from another host.
They run before every release, and one failure stops the release. A test written by the same person who wrote the code can share its mistake, which is why there is a second program too.
A second program, written separately
For each tool that produces numbers, a Python program works the answers out again from the rules as the page states them. It uses exact arithmetic (decimal numbers to 50 digits, or fractions), so there is no floating-point rounding to agree with. It shares no code with the page. It then loads the page's own JavaScript, runs the same cases through it and compares every answer. One disagreement and it exits with an error.
| Guards | What the separate program works out and compares | Compared |
|---|---|---|
Amortization calculator and schedule, mortgage and loan calculators, and the Excel file they exportcross-check-amortization.py | The payment, the number of payments, every date and the balance after every payment, in 50-digit decimal arithmetic, for scenarios with extra payments, balloon loans, a zero rate and a start on the 31st. It also reads every formula in the exported Excel workbook, works it out, and compares the answer with the value stored beside it. | 22 scenarios |
Credit card payoff calculatorcross-check-credit-card-strategies.py | Month by month, every card's balance, the month each card is cleared and the total interest, for avalanche, snowball and minimum-only plans, from one card up to four. | 30 plans |
Simple interest calculatorcross-check-simple-interest-modes.py | Interest for years, months or days on a 365- or 360-day year, rates quoted per year, month or day, and the days between two dates, counted with Python's own calendar. | 304 cases |
Percentage calculatorscross-check-percentage-modes.py | All seven modes in exact fractions: percent of, what percent, percentage change and difference, increase or decrease by a percent, and working back to the original. | 625 cases |
Discount calculatorcross-check-discount-modes.py | Percent off, amount off, stacked discounts (two 20% discounts make 36% off, not 40%), tax, and working back from the sale price, in exact fractions. | 619 cases |
Commission and real estate commission calculatorscross-check-commission-modes.py | Flat and tiered commission (each band a percentage or a fixed amount), base pay, the agent and brokerage split, and the sale needed for a target. | 378 cases |
Tip calculatorcross-check-tip.py | The tip, an equal split and a split by what each person ordered, in whole cents with halves rounded up. The shares must add up to the total exactly. | 8,770 cases |
JSON formatter and validatorcross-check-json-validity.py | Whether a text is valid JSON, against Python's own json module, for valid documents and for thousands of broken variants of them. | 4,092 documents (3,225 broken) |
JSON beautifier and validatorcross-check-json-beautify.py | How documents are laid out in every combination of indent, key order, short arrays and escapes; which Unicode characters are shown as themselves; the size and warnings the validator reports; and where, and why, a broken document stops, against Python's own JSON error. | 24,000 layouts, 1,112,000 characters, 4,743 broken documents |
Random word generatorcross-check-random-word.py | Which words pass each filter; that a pick is unbiased, by counting every outcome of a small random source; draws without repeats; the seeded sample; and the bits-of-randomness figures. | 4,720 cases |
Nine of the ten run inside the test step of every release (and are skipped, with a message, on a machine without Python). The other one, the JSON beautifier and validator check, is run by hand and by the check of the checks below.
Six older scripts are not counted. They re-derive a few worked examples in Python and print them beside the values pinned in the unit tests. They do not run the page's code, so they cannot catch a bug in it. Together with the ten above, they make the 16 Python scripts in the repository.
cross-check-commission.py: flat, tiered and reverse commissioncross-check-credit-card-payoff.py: months to pay off and interest for one cardcross-check-discount.py: a discount, stacked discounts, tax and the reversecross-check-json-formatter.py: a short list of JSON texts and Python's verdict on eachcross-check-percentage.py: percent of, what percent and percentage changecross-check-simple-interest.py: the closed formula against a day-by-day loop
Checking the checks
A check that cannot fail is not a check. So we break the code on purpose. One command edits an engine file to plant a bug (for example, charging interest on the balance plus one cent), runs the check that should catch it, and puts the file back. At the last full run, on October 11, 2026 (commit 39a349c), 37 of 37 planted bugs were caught, the unchanged code passed every check, and the files were restored byte for byte.
| Planted bug | Caught by | It printed |
|---|---|---|
| Amortization: interest charged on the balance plus a cent | cross-check-amortization.py | 22 scenario(s), 20 schedule failure(s), 19 workbook failure(s) |
| Percentage: X is Y% of what, forgets the /100 | cross-check-percentage-modes.py | 625 case(s) compared, 123 mismatch(es) |
| Credit card: avalanche sorts lowest APR first | cross-check-credit-card-strategies.py | 30 plan(s) checked, 7 failure(s) |
| Simple interest: 366-day default year | cross-check-simple-interest-modes.py | 304 case(s) compared, 9 mismatch(es) |
| Tip: the odd cents of an equal split are lost (the high share equals the low one) | cross-check-tip.py | MISMATCHES: 2468 |
| JSON: tree parser accepts raw control characters in strings | cross-check-json-validity.py | 4092 document(s) compared (3225 invalid), 26 disagreement(s) about validity |
The first time we ran it, on September 30, 2026, it held eight planted bugs and caught six. The two it missed pointed at paths the checks never exercised: the sale needed for a target take-home in the commission calculator, and the default day count in the simple interest calculator. Both are covered now.
A worked disagreement
The tip calculator shares a bill in whole cents. Split $118.00 between three people and the shares are $39.34, $39.33 and $39.33: one person pays the odd cent, and the three add up to $118.00 exactly. We planted this bug in the code that gives one person the larger share:
before: high: rest > 0 ? low + 1 : low, after: high: low,
| Who worked it out | The three shares | Total |
|---|---|---|
| The page's code, with the bug | $39.33, $39.33, $39.33 | $117.99 |
| The separate Python program | $39.34, $39.33, $39.33 | $118.00 |
One cent had vanished. Across the check's 8,770 cases the two programs disagreed 2,468 times. The check printed the first few, including the line shares do not add up to the total, and exited with an error, which stops a release. Without the bug, the same check reports 0 disagreements.
Check one yourself
- A loan payment. The mortgage page's example is $360,000 at 7.03% for 30 years. In Excel or Google Sheets,
=-PMT(7.03%/12,30*12,360000)gives $2,402.35. In Python,python3 -c "r=0.0703/12; print(round(360000*r/(1-(1+r)**-360), 2))"prints 2402.35. The calculator shows $2,402.35 too. - A split bill.
python3 -c "print(divmod(11800, 3))"prints (3933, 1): 3,933 cents each and one cent left over, so $39.34, $39.33 and $39.33. - A large number in JSON. JavaScript's own
JSON.parseturns 1234567890123456789 into 1234567890123456800. Paste{"id": 1234567890123456789}into the JSON formatter and the number comes back as you wrote it.
What this does not cover
- The same team wrote both sides. The code, the tests and the second programs were written by the same team, with the help of AI assistants. They catch coding mistakes, such as a wrong sign or a lost cent. They cannot catch a rule that all of us misread, which is why each page names the source for its rules.
- Outside numbers are read, not recomputed. The Freddie Mac averages of September 24, 2026 and the Federal Reserve G.19 rates for the second quarter of 2026 are quoted with their dates, and they will go stale.
- A word list cannot be checked by a program. The random word generator's words are written by hand. Its tests check the structure of the lists and a blocklist, not taste.
- Wording is tested separately. The checks above are about numbers. How a page words an error for a nonsense entry is tested in a browser, and is not part of what is described here.
- The checks are our own. No outside firm has audited them.
- This page quotes a dated run. The release gate re-runs the tests, and the checks that are part of it, on every release.
The exact commands
The source repository is private. These are the commands our release process runs, so a reviewer who is given the repository can repeat every number on this page.
| What it does | Command |
|---|---|
| Run one test file (there is one or more per area) | node tests/tip-engine.test.js |
| Run one check: exit status 0 means no disagreement | python scripts/cross-check-tip.py |
| Plant the bugs one at a time, in a clean copy of the repository | python scripts/mutation-check-crosschecks.py |
| Run every check and write the record this page quotes | python scripts/record-checks-run.py --root <clean copy> |
| Confirm every figure on every page matches the code that computes it | node scripts/prerender.js --check |
| Run every release gate without deploying | python deploy/ship.py --dry-run |
If a figure is wrong
Tell us. Contact says what to include. If we confirm an error we correct the page, and its Updated date changes.