Mortgage Calculator — Swift source
Compute monthly payments, the full amortization schedule and total interest, with extra-payment what-if analysis showing months and interest saved — runs entirely in your browser.
This is the Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.
// mortgage-calculator — amortized monthly payment.
//
// Language: Swift (5.9, standard library only)
// Source: CosmoDev polyglot showcase port of the Mortgage Calculator
// tool, ported from src/lib/mortgage.ts (the canonical
// TypeScript implementation).
// Tool: https://dev.cosmolabs.org/tools/mortgage-calculator
// License: display source — part of CosmoDev's polyglot tool pages.
//
// Display snippet: ports the core monthlyPayment() formula from the
// TypeScript lib. The full amortization schedule (integer-cent tracking,
// extra payments, payoff dates) lives in the TypeScript/Go sources.
//
// Semantics: standard amortization formula P*r*(1+r)^n / ((1+r)^n - 1)
// with r = ratePct/100/12 and n = years*12. A zero rate divides the
// principal evenly across n months. Returns the payment rounded to
// cents, or nil on invalid input (non-positive principal, non-positive
// years, non-finite values).
import Foundation
enum MortgageCalculator {
/// Round to 2 decimals, half-up (mirrors the TS Math.round).
static func round2(_ n: Double) -> Double {
(n * 100.0 + 0.5).rounded(.down) / 100.0
}
static func monthlyPayment(principal: Double, ratePct: Double, years: Int) -> Double? {
guard principal.isFinite, ratePct.isFinite else { return nil }
guard principal > 0, years > 0 else { return nil }
let r = ratePct / 100.0 / 12.0
let n = Double(years * 12)
let payment: Double
if r == 0.0 {
payment = principal / n
} else {
let growth = pow(1.0 + r, n)
payment = principal * r * growth / (growth - 1.0)
}
return round2(payment)
}
}
// Example:
// monthlyPayment(principal: 300000, ratePct: 6, years: 30) -> 1798.65
let result = MortgageCalculator.monthlyPayment(principal: 300_000, ratePct: 6, years: 30)
precondition(result == 1798.65, "expected 1798.65, got \(String(describing: result))")
// A zero rate divides the principal evenly.
precondition(MortgageCalculator.monthlyPayment(principal: 300_000, ratePct: 0, years: 30) == 833.33,
"expected 833.33 at a zero rate")
// Invalid inputs are rejected, never clamped.
precondition(MortgageCalculator.monthlyPayment(principal: -1, ratePct: 6, years: 30) == nil, "non-positive principal")
precondition(MortgageCalculator.monthlyPayment(principal: 300_000, ratePct: 6, years: 0) == nil, "non-positive years")
precondition(MortgageCalculator.monthlyPayment(principal: .infinity, ratePct: 6, years: 30) == nil, "non-finite value")
print("ok")
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →