Functional Weave
Code in TypeScript

money.apply-rate

Apply a rate expressed in basis points to a monetary amount, with an explicit rounding mode.

1.0.1 · published 2026-10-03 by charlie · Anterra

Pinned by 22 tests, run in TypeScript, Python and Rust.

What it does

Rates are integers in basis points, never floats. A VAT rate of 20% is 2000, not 0.2, so the calculation stays in exact integer arithmetic from input to rounded result.

This single function covers VAT, discounts, interest, commission and service charges: they differ in the rate and the rounding policy, not in the arithmetic.

For example

  • applyRate(£100.00, 20%, half-up) → £20.00 20 percent VAT on 100 pounds
  • applyRate(£1.99, 20%, half-up) → £0.40 20 percent of 1.99 rounds up
  • applyRate(£49.99, 5%, half-up) → £2.50 5 percent reduced rate

The function

The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.

export function applyRate(amount: Money, basisPoints: number, mode: RoundingMode): Money
amountMoney
basisPointsint10000 = 100%, 2000 = 20%, 25 = 0.25%
modeRoundingMode
returnsMoney

Your code names it in one line, in the file that uses it

import { applyRate } from "#fune/money.apply-rate@^1";
impl/typescript.ts · 21 lines · open · raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

import { type RoundingMode, roundDiv } from "./math_round_div.ts";  ← from math.round-div ^1.0.0 · built alongside by fune
import { type Money, money } from "./money_amount.ts";  ← from money.amount ^1.0.0 · built alongside by fune

/**
 * Apply a rate given in basis points (10000 = 100%).
 *
 * Rates are integers, never floats, so VAT, discounts, interest and
 * commission all stay in exact arithmetic right up to the single rounding
 * step the caller asked for.
 */
export function applyRate(amount: Money, basisPoints: number, mode: RoundingMode = "half-up"): Money {
  if (!Number.isInteger(basisPoints)) {
    throw new TypeError(`basisPoints must be an integer, received ${basisPoints}`);
  }
  return money(roundDiv(amount.minor * basisPoints, 10000, mode), amount.currency);
}

/** The complement: what is left after the rate is taken off. */
export function applyRateComplement(amount: Money, basisPoints: number, mode: RoundingMode = "half-up"): Money {
  return money(amount.minor - applyRate(amount, basisPoints, mode).minor, amount.currency);
}

Install

fune build

With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and its 2 dependencies, pins them in fune.lock, downloads only the TypeScript package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:

fune add money.apply-rate
Download for TypeScript money.apply-rate-1.0.1-typescript.fune · 6,823 bytes sha256 774c8d221a8e733126412580062bf5d04d24ace2412f478180741700c04d682c

The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./money.apply-rate-1.0.1-typescript.fune, or fetch it from a terminal with fune pull money.apply-rate@1.0.1:typescript.

The whole function, every language, is one file too: money.apply-rate-1.0.1.fune, 8,862 bytes, sha256 e34dd8f310c3432e1767a88c32e75318e3764f0ec7a0b3f941fa8e19e4e2ce1b. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

// fune: before money.apply-rate

after — your function gets the result and the arguments, and returns the final result.

// fune: after money.apply-rate

replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.

// fune: replace math.round-div in money.apply-rate
// fune: replace money.amount in money.apply-rate

step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show money.apply-rate --steps.

// fune: step money.apply-rate after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

CaseArgumentsExpected
20 percent VAT on 100 pounds £100.00, 20%, half-up → £20.00
20 percent of 1.99 rounds up £1.99, 20%, half-up → £0.40
5 percent reduced rate £49.99, 5%, half-up → £2.50
exact half rounds up under half-up £0.05, 50%, half-up → £0.03
exact half rounds to even under half-even £0.05, 50%, half-even → £0.02
down never rounds up £1.99, 20%, down → £0.39
zero rate £123.45, 0%, half-up → £0.00
100 percent is the amount itself £123.45, 100%, half-up → £123.45
quarter of a percent £1,000.00, 0.25%, half-up → £2.50
credits rate symmetrically -£1.99, 20%, half-up → -£0.40
Show the other 12 tests
CaseArgumentsExpected
a negative half rounds away from zero under half-up, not towards it as Math.round does -£0.05, 50%, half-up → -£0.03
half-even rounds an odd quotient's half up £0.15, 50%, half-even → £0.08
up rounds any fraction away from zero £1.01, 1%, up → £0.02
up on a credit rounds away from zero too -£1.01, 1%, up → -£0.02
down on a credit truncates towards zero -£1.99, 20%, down → -£0.39
a rate above 100 percent £10.00, 150%, half-up → £15.00
a negative rate, as a discount £19.99, -10%, half-up → -£2.00
a large amount stays exact £9,007,199,254.74, 20%, half-up → £1,801,439,850.95
zero-decimal currency ¥1,234, 10%, half-even → ¥123
a zero amount is zero at any rate ¥0, 20%, up → ¥0
an unknown rounding mode is an error, not a default £1.00, 20%, nearest → error: unknown rounding mode
an invalid currency on the amount is an error 1.00 gbp, 20%, half-up → error: ISO 4217

More from the author

1.0.1 adds tests; behaviour unchanged.

Files

PathBytes
README.md388
impl/python.py948
impl/rust.rs1,001
impl/typescript.ts952
vectors.json3,543