Conversions Length And Distance

Detailed reference converted from the original conversions-length-and-distance.html page.

Loan Details
Taxes, Insurance & Fees (Optional)
Advanced Settings (Currency & Frequency)

How to use

Enter the loan amount, interest rate, and term. Optionally add extra principal and escrow-related costs (taxes, insurance, PMI, HOA). Results update automatically after using the Calculate button or inline edits.

Methodology

The calculator relies on a fixed-rate amortization model. Each payment covers interest first, then principal, and any extra principal is applied immediately to shorten the payoff horizon.

Full original guide (expanded)

<header id="siteHeader">
  <div class="nav container">
    <a class="brand" href="https://calcdomain.com">CalcDomain</a>
    <div class="navRight">
      <a class="pill" href="https://calcdomain.com/search">Advanced Search</a>
      <span class="pill">Categories</span>
    </div>
  </div>
</header><div class="container">
  <main style="margin:24px 0 40px;" id="mainContent">
    <h1 id="pageTitle">conversions-length-and-distance</h1>
    <p class="sub" id="pageSub"></p>
    <div id="errorBox" class="alert" role="alert" aria-live="polite"></div>

    <div class="calc-hero" id="calcHero">
      <section class="card" id="inputsCard" aria-label="Calculator inputs">
<div data-canon-btnrow="1" style="display:flex; gap:10px; margin-top:12px; flex-wrap:wrap;">
<button id="calcBtn" type="button" class="btn">Calculate</button>

<button id="resetBtn" type="button" class="btn btnSecondary">Reset</button>
</div>
</section>
      <section class="card sticky-box" id="resultsCard" aria-label="Calculator results"></section>
    </div>

    <section class="card content-card" id="howToUse" aria-label="How to use and methodology">
      <h2>Full original guide (expanded)</h2>
    <hr class="auditspine-hr"><section aria-label="Formulas, sources, changelog, verification">
  <span class="auditspine-badge">Audit: Complete</span>

  <details style="margin-top:10px">
    <summary><strong>Formula (LaTeX) + variables + units</strong></summary>
    <div class="auditspine-note" style="margin-top:10px">
      This section shows the formulas used by the calculator engine, plus variable definitions and units.
      
    </div>
    <div class="auditspine-formula">
      <div style="margin:10px 0">
  <div><strong>Unit conversion</strong></div>
  <div style="margin-top:6px">\[y = x\cdot k\]</div>
  <pre class="auditspine-mono">y = x * k</pre>
</div>
      <div style="margin-top:12px"><strong>Variables and units</strong></div>
      <ul style="margin:8px 0 0 18px">
        <li><em>No variables provided in audit spec.</em></li>
      </ul>
    </div>
  </details>

  <div class="auditspine-note" style="margin-top:12px">
    <strong>Sources (authoritative):</strong>
    <ul style="margin:8px 0 0 18px">
      <li><strong>NIST — Weights and measures</strong> — nist.gov · Accessed 2026-01-19<br><a href="https://www.nist.gov/pml/weights-and-measures" target="_blank" rel="nofollow noopener">https://www.nist.gov/pml/weights-and-measures</a></li>
<li><strong>NIST — SI units</strong> — nist.gov · Accessed 2026-01-19<br><a href="https://www.nist.gov/pml/owm/metric-si/si-units" target="_blank" rel="nofollow noopener">https://www.nist.gov/pml/owm/metric-si/si-units</a></li>
    </ul>
  </div>

  <div class="auditspine-changelog" style="margin-top:12px">
    <strong>Changelog</strong><br>
    <div style="margin-top:6px">
      <strong>Version:</strong> 0.1.0-draft<br>
      <strong>Last code update:</strong> 2026-01-19
    </div>
    <div style="margin-top:10px">
  <strong>0.1.0-draft</strong> · 2026-01-19
  <ul style="margin:6px 0 0 18px">
    <li>Initial audit spec draft generated from HTML extraction (review required).</li>
<li>Verify formulas match the calculator engine and convert any text-only formulas to LaTeX.</li>
<li>Confirm sources are authoritative and relevant to the calculator methodology.</li>
  </ul>
</div>
  </div>

  <div class="auditspine-note" style="margin-top:12px">
    <strong>Verified by Ugo Candido on 2026-01-19</strong><br>
    <a href="https://calcdomain.com/ugocandido" target="_blank" rel="noopener">Profile</a> ·
    <a href="https://www.linkedin.com/in/ugocandido92821/" target="_blank" rel="noopener">LinkedIn</a>
  </div>
</section></section>

    <section class="meta-section" id="pageMeta" aria-label="Verification, changelog, formulas, citations">
  <details id="formulaDetails">
    <summary style="cursor:pointer; font-weight:700; color:var(--text)">Formulas</summary>
    <div style="margin-top:12px; padding:12px; background:#fff; border:1px solid var(--line); border-radius:8px;">
      <p style="margin:0;">(Formulas preserved from original page content, if present.)</p>
    </div>
  </details>

  <details id="citationDetails" style="margin-top:10px;">
    <summary style="cursor:pointer; font-weight:700; color:var(--text)">Citations</summary>
    <div style="margin-top:12px; padding:12px; background:#fff; border:1px solid var(--line); border-radius:8px;">
      <p style="margin:0;">(Citations preserved from original page content, if present.)</p>
    </div>
  </details>

  <details id="changelogDetails" style="margin-top:10px;">
    <summary style="cursor:pointer; font-weight:700; color:var(--text)">Changelog</summary>
    <div style="margin-top:12px; padding:12px; background:#fff; border:1px solid var(--line); border-radius:8px;">
      <ul style="padding-left:18px; margin:0;">
        <li>0.1.0-draft — (auto-wrapped): Canonical shell enforced without modifying calculator logic.</li>
      </ul>
    </div>
  </details>

  <div class="badge-row" id="badgeRow">
    <div class="badge" id="versionBadge">Version 0.1.0-draft</div>
  </div>
</section>
  </main>
</div><footer id="siteFooter" class="bg-gray-900 text-white py-12">
  <div class="container mx-auto px-4">
    <div class="border-t border-gray-800 pt-8 text-center text-gray-400">
      <p>© 2025 CalcDomain. All Rights Reserved. | Free Online Calculators for Everyone</p>
    </div>
</footer>
<script>
  (function() {
    'use strict';
  })();
</script>
Formulas

Fixed-rate amortization (principal & interest):

\[M = P \cdot \frac{r(1+r)^{n}}{(1+r)^{n}-1}\]
  • $P$: Principal (loan amount)
  • $r$: Periodic interest rate (annual rate / payments per year)
  • $n$: Total payments (years × payments per year)
Citations
Changelog
  • v1.0.0 — Canonical layout & calculator refactor (aligns with mortgage-payment canonical behavior).
  • v0.1.0-draft — Original conversions reference preserved inside the expanded guide.
Verified by Ugo Candido
Last Updated: 2026-01-19
Version 1.5.0
; return `${symbol}${round2(safe).toFixed(2)}`; } }; const payoffLabel = (paymentsCount, freq) => { if (!Number.isFinite(paymentsCount) || paymentsCount <= 0 || !Number.isFinite(freq) || freq <= 0) { return '—'; } const years = paymentsCount / freq; const wholeYears = Math.floor(years); const remainderMonths = Math.round((years - wholeYears) * 12); if (wholeYears <= 0) return `${Math.max(1, remainderMonths)} months`; if (remainderMonths <= 0) return `${wholeYears} years`; return `${wholeYears} yrs ${remainderMonths} mos`; }; function computeAmortization({ principal, annualRatePct, years, extraPerPayment, paymentsPerYear }) { const P = principal; const n = Math.round(years * paymentsPerYear); const r = (annualRatePct / 100) / paymentsPerYear; if (!(P > 0) || !(n > 0) || !(r >= 0) || !Number.isFinite(P) || !Number.isFinite(n) || !Number.isFinite(r)) { return null; } let M; if (r === 0) { M = P / n; } else { const pow = Math.pow(1 + r, n); M = P * (r * pow) / (pow - 1); } if (!Number.isFinite(M) || M <= 0) return null; let balance = P; let totalInterest = 0; const schedule = []; const MAX_ITERS = n + 5000; for (let i = 1; i <= MAX_ITERS; i++) { if (balance <= 0.005) break; const interest = balance * r; let principalPaid = M - interest; if (principalPaid < 0) return null; let extra = extraPerPayment; if (principalPaid + extra > balance) { extra = Math.max(0, balance - principalPaid); principalPaid = Math.min(principalPaid, balance); } balance = balance - principalPaid - extra; totalInterest += interest; schedule.push({ num: i, total: round2(M + extra), int: round2(interest), prin: round2(principalPaid), extra: round2(extra), bal: round2(Math.max(0, balance)) }); } if (schedule.length === 0) { return null; } return { paymentPerPeriod: M, totalInterest: round2(totalInterest), schedule }; } const els = { loanAmount: document.getElementById('loanAmount'), interestRate: document.getElementById('interestRate'), loanTermYears: document.getElementById('loanTermYears'), extraPrincipal: document.getElementById('extraPrincipal'), annualTax: document.getElementById('annualTax'), annualInsurance: document.getElementById('annualInsurance'), monthlyPMI: document.getElementById('monthlyPMI'), monthlyHOA: document.getElementById('monthlyHOA'), currency: document.getElementById('currency'), paymentsPerYear: document.getElementById('paymentsPerYear'), totalMonthly: document.getElementById('totalMonthly'), piMonthly: document.getElementById('piMonthly'), tiMonthly: document.getElementById('tiMonthly'), totalInterest: document.getElementById('totalInterest'), payoffTime: document.getElementById('payoffTime'), errorBox: document.getElementById('errorBox'), scheduleWrap: document.getElementById('scheduleWrap'), scheduleBody: document.getElementById('scheduleBody'), toggleSchedule: document.getElementById('toggleSchedule'), downloadCsv: document.getElementById('downloadCsv'), calcBtn: document.getElementById('calcBtn'), resetBtn: document.getElementById('resetBtn') }; const defaults = { loanAmount: '350000', interestRate: '6.5', loanTermYears: '30', extraPrincipal: '0', annualTax: '0', annualInsurance: '0', monthlyPMI: '0', monthlyHOA: '0', currency: 'USD', paymentsPerYear: '12' }; let currentSchedule = []; let latestInputs = null; let scheduleVisible = false; function parseInputs() { return { principal: parseFloat(els.loanAmount.value), annualRatePct: parseFloat(els.interestRate.value), years: parseFloat(els.loanTermYears.value), extraPerPayment: parseFloat(els.extraPrincipal.value) || 0, paymentsPerYear: parseInt(els.paymentsPerYear.value, 10), currency: els.currency.value, annualTax: parseFloat(els.annualTax.value) || 0, annualInsurance: parseFloat(els.annualInsurance.value) || 0, monthlyPMI: parseFloat(els.monthlyPMI.value) || 0, monthlyHOA: parseFloat(els.monthlyHOA.value) || 0 }; } function validate(inputs) { const errors = []; const allowedFreq = new Set([12, 26, 52]); if (!Number.isFinite(inputs.principal) || inputs.principal <= 0) errors.push('Loan amount must be greater than 0.'); if (!Number.isFinite(inputs.years) || inputs.years <= 0) errors.push('Term (years) must be greater than 0.'); if (!Number.isFinite(inputs.annualRatePct) || inputs.annualRatePct < 0) errors.push('Interest rate must be 0 or greater.'); if (!Number.isFinite(inputs.extraPerPayment) || inputs.extraPerPayment < 0) errors.push('Extra principal must be 0 or greater.'); if (!allowedFreq.has(inputs.paymentsPerYear)) errors.push('Payment frequency is invalid.'); if (inputs.annualTax < 0 || inputs.annualInsurance < 0 || inputs.monthlyPMI < 0 || inputs.monthlyHOA < 0) { errors.push('Taxes/insurance/fees must be 0 or greater.'); } return { ok: errors.length === 0, errors }; } function compute(inputs) { const amortization = computeAmortization(inputs); if (!amortization) return null; const paymentPerPeriod = amortization.paymentPerPeriod; const monthlyPI = paymentPerPeriod * (inputs.paymentsPerYear / 12); const monthlyEscrow = (inputs.annualTax + inputs.annualInsurance) / 12 + inputs.monthlyPMI + inputs.monthlyHOA; return { schedule: amortization.schedule, monthlyPI, monthlyEscrow, totalMonthly: monthlyPI + monthlyEscrow, totalInterest: amortization.totalInterest, payoff: `${amortization.schedule.length} payments (~${payoffLabel(amortization.schedule.length, inputs.paymentsPerYear)})` }; } function format(outputs, inputs) { if (!outputs) return null; return { totalMonthly: fmtCurrency(outputs.totalMonthly, inputs.currency), piMonthly: fmtCurrency(outputs.monthlyPI, inputs.currency), tiMonthly: fmtCurrency(outputs.monthlyEscrow, inputs.currency), totalInterest: fmtCurrency(outputs.totalInterest, inputs.currency), payoff: outputs.payoff }; } function renderError(messages) { if (!messages || messages.length === 0) { els.errorBox.style.display = 'none'; els.errorBox.textContent = ''; return; } els.errorBox.style.display = 'block'; els.errorBox.textContent = messages.join(' '); } function renderTable(currency) { els.scheduleBody.innerHTML = ''; const rows = currentSchedule.slice(0, 360); const frag = document.createDocumentFragment(); rows.forEach((row) => { const tr = document.createElement('tr'); tr.innerHTML = ` ${row.num} ${fmtCurrency(row.total, currency)} ${fmtCurrency(row.int, currency)} ${fmtCurrency(row.prin, currency)} ${fmtCurrency(row.extra, currency)} ${fmtCurrency(row.bal, currency)} `; frag.appendChild(tr); }); els.scheduleBody.appendChild(frag); } function setScheduleVisibility(visible) { scheduleVisible = visible; if (visible) { els.scheduleWrap.style.display = 'block'; els.toggleSchedule.textContent = 'Hide Amortization Schedule'; els.toggleSchedule.setAttribute('aria-expanded', 'true'); if (latestInputs && currentSchedule.length) { renderTable(latestInputs.currency); } } else { els.scheduleWrap.style.display = 'none'; els.toggleSchedule.textContent = 'View Amortization Schedule'; els.toggleSchedule.setAttribute('aria-expanded', 'false'); } } function render(formatted, errors) { if (errors && errors.length) { renderError(errors); els.downloadCsv.disabled = true; setScheduleVisibility(false); return; } renderError([]); if (!formatted) return; els.piMonthly.textContent = formatted.piMonthly; els.tiMonthly.textContent = formatted.tiMonthly; els.totalMonthly.textContent = formatted.totalMonthly; els.totalInterest.textContent = formatted.totalInterest; els.payoffTime.textContent = formatted.payoff; els.downloadCsv.disabled = currentSchedule.length === 0; if (scheduleVisible && latestInputs && currentSchedule.length) { renderTable(latestInputs.currency); } } function update() { const inputs = parseInputs(); const validation = validate(inputs); if (!validation.ok) { currentSchedule = []; latestInputs = null; render(null, validation.errors); return; } const outputs = compute(inputs); if (!outputs) { currentSchedule = []; latestInputs = null; render(null, ['Calculation failed for the current inputs. Please revise values.']); return; } currentSchedule = outputs.schedule; latestInputs = inputs; const formatted = format(outputs, inputs); render(formatted, []); } function resetInputs() { Object.entries(defaults).forEach(([id, value]) => { if (els[id]) els[id].value = value; }); currentSchedule = []; latestInputs = null; setScheduleVisibility(false); els.downloadCsv.disabled = true; renderError([]); } function toggleScheduleVisibility() { setScheduleVisibility(!scheduleVisible); } function downloadCsv() { if (!currentSchedule.length || !latestInputs) return; const headers = ['Payment #', 'Total Payment', 'Interest', 'Principal', 'Extra', 'Balance']; const lines = [headers.join(',')]; currentSchedule.forEach(row => { const cells = [ row.num, fmtCurrency(row.total, latestInputs.currency), fmtCurrency(row.int, latestInputs.currency), fmtCurrency(row.prin, latestInputs.currency), fmtCurrency(row.extra, latestInputs.currency), fmtCurrency(row.bal, latestInputs.currency) ]; lines.push(cells.join(',')); }); const blob = new Blob([lines.join('\n')], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = 'amortization-schedule.csv'; link.style.display = 'none'; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(link.href); } element.addEventListener('change', debouncedUpdate); }); els.calcBtn.addEventListener('click', update); els.resetBtn.addEventListener('click', () => { resetInputs(); const debouncedUpdate = debounce(update, 100); document.querySelectorAll('#inputsCard input, #inputsCard select, #inputsCard textarea') .forEach((el) => { el.addEventListener('input', debouncedUpdate); el.addEventListener('change', debouncedUpdate); }); update(); }); els.toggleSchedule.addEventListener('click', toggleScheduleVisibility); els.downloadCsv.addEventListener('click', downloadCsv); resetInputs(); update(); })();