I've been building a set of free online calculators (loans, mortgages, salary, taxes, investments, and more) and the most common question from users is: "Why doesn't your result match my bank's?"
The answer is almost never "the math is wrong." It's usually that two calculators made different assumptions. Here are the ones that matter, with code.
1. Compounding frequency changes the answer
The standard compound interest formula is:
A = P * (1 + r/n)^(n*t)
where P is principal, r is the annual rate, n is compounding periods per year, and t is years.
function compound(principal, annualRate, timesPerYear, years) {
return principal * Math.pow(1 + annualRate / timesPerYear, timesPerYear * years);
}
compound(10000, 0.05, 1, 10); // ~16288.95 (annual)
compound(10000, 0.05, 12, 10); // ~16470.09 (monthly)
Same principal, same rate, same time. About $181 apart, purely because of compounding frequency. If your calculator assumes annual and your bank compounds monthly, you'll see a mismatch.
2. APR and APY are not the same number
APR is the stated yearly rate. APY includes the effect of compounding:
APY = (1 + r/n)^n - 1
const apy = (rate, n) => Math.pow(1 + rate / n, n) - 1;
apy(0.05, 12); // ~0.05116, or about 5.116%
A 5% APR compounded monthly is roughly a 5.116% APY. Mixing these up is one of the most common bugs in finance UIs.
3. Loan payments use the amortization formula
For a fixed-rate loan with monthly payments:
M = P * r(1+r)^n / ((1+r)^n - 1)
where r is the monthly rate and n is the total number of payments.
function monthlyPayment(principal, annualRate, years) {
const r = annualRate / 12;
const n = years * 12;
if (r === 0) return principal / n; // don't divide by zero
const factor = Math.pow(1 + r, n);
return (principal * r * factor) / (factor - 1);
}
monthlyPayment(200000, 0.06, 30); // ~1199.10
Note the zero-rate edge case. Skipping it gives you NaN and an angry bug report.
4. Floating point will bite you
0.1 + 0.2; // 0.30000000000000004
Math.round(1.005 * 100) / 100; // 1 (not 1.01!)
For money, use one of these approaches:
- Work in integer cents and only convert for display
- Use a decimal library such as decimal.js or big.js for anything involving repeated multiplication (like amortization schedules)
- Round at defined points, not after every step, and document which
Rounding at every step of a 360-payment schedule can drift from a bank that rounds only at payment time, or vice versa.
5. Other reasons results differ from a lender
Even with correct formulas, real-world numbers can diverge because of:
- Payment timing (start vs end of period)
- Day-count conventions (actual/365 vs 30/360)
- Fees, insurance, and promotional rates
- Taxes and withholding rules that vary by region
- Rounding rules specific to the institution
For that reason, I label every result as an estimate, and I'd suggest you do the same in anything you build.
What I'd suggest if you're building your own
- Show your assumptions next to the result (compounding, payment timing, rounding).
- Write unit tests with known-good values, such as published amortization examples.
- Handle edge cases: zero rates, zero terms, negative inputs, very large values.
- Separate formula code from UI code so calculators are testable and reusable.
Try the calculators
These formulas power calculators like compound interest, mortgage, and loan payments on Noloii, a free set of 100+ calculators across 13 categories (credit, salary, tax, investment, real estate, and more). Each one explains its own assumptions.
If you find a result that doesn't match a source you trust, tell me in the comments with your inputs. Those reports are the fastest way I find edge cases.
What's the nastiest rounding or floating-point bug you've hit in a money-related project?


Top comments (1)