Functional Weave
Code in TypeScript

lending.loan-payment@1.0.0

README.md

2,504 bytes · view raw

# lending.loan-payment

The level (annuity) payment that repays a loan in equal instalments: what a
mortgage, car loan or personal loan quote shows as "monthly payment".

## The formula

With a nominal annual rate split equally between the periods,
r = rate / paymentsPerYear, and n payments:

    payment = P × r / (1 − (1 + r)^−n)

That is the textbook present-value-of-an-annuity formula, the same one as a
spreadsheet's PMT. Here it is never evaluated in floating point. The rate is
b basis points, so r = b / D with D = 10000 × paymentsPerYear, and the formula
is exactly the fraction

    P × b × (D + b)^n  /  ( D × ((D + b)^n − D^n) )

Both sides are integers. For a 25-year monthly mortgage (D + b)^n is a
number of over 1,500 digits, far past what `math.rational` holds (its parts
must stay within 2^53 so JavaScript numbers stay exact), so the fraction is
built with arbitrary-size integers: `bigint` in TypeScript, `int` in Python and
`math.big-integer` in Rust. It is then rounded exactly once, with the
`math.round-div` mode you pass. A zero rate is P / n, rounded the same way.

## Rounding

- `half-up` gives the nearest penny, what most quotes show: £100,000 at 5%
  over 25 years is £584.59.
- `up` never under-repays: every payment is at most a penny high, and the
  final payment (see lending.amortisation-schedule) comes out slightly
  smaller.
- `down` or `half-even` are available where a contract says so.

The mode applies to the exact value, so there is no double rounding: a
payment of exactly x.5 pence rounds by the mode's tie rule, anything else to
the nearer penny.

## What it does not do

- The rate is nominal and divided equally (12% a year is 1% a month). It is
  not an APR or AER; lending.apr and banking.savings-aer convert.
- Every period is treated as equal. Interest charged daily, or a first period
  of odd length, changes the payment slightly; build the schedule with
  lending.daily-interest when that matters.
- No fees are added, and the payment is not a regulated APR disclosure.

## Limits

The rate is 0 to 100000 basis points, payments per year 1 to 52, and the term
must hold a whole number of payments (13 months cannot be paid quarterly), at
most 3000 of them. The principal must be positive and, for TypeScript to stay
exact, below 2^51 minor units.

The module also exports `paymentCount` (the argument checks and n) and
`roundWide` (round a large positive fraction with a round-div mode), which
the other lending capabilities reuse.