Skip to content

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 →