Functional Weave
Code in Rust

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

  • apply_rate(£100.00, 20%, half-up) → £20.00 20 percent VAT on 100 pounds
  • apply_rate(£1.99, 20%, half-up) → £0.40 20 percent of 1.99 rounds up
  • apply_rate(£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.

pub fn apply_rate(amount: &Money, basis_points: i64, mode: &str) -> Money
amountMoney
basis_pointsint10000 = 100%, 2000 = 20%, 25 = 0.25%
modeRoundingMode
returnsMoney

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

fune!(money.apply-rate@^1);  // then call apply_rate(…)
impl/rust.rs · 31 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.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::math_round_div::round_div;  ← from math.round-div ^1.0.0 · built alongside by fune
use super::money_amount::{money, money_from_value, money_to_value, Money};  ← 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.
pub fn apply_rate(amount: &Money, basis_points: i64, mode: &str) -> Money {
    money(
        round_div(amount.minor * basis_points, 10000, mode),
        &amount.currency,
    )
}

/// The complement: what is left after the rate is taken off.
pub fn apply_rate_complement(amount: &Money, basis_points: i64, mode: &str) -> Money {
    money(
        amount.minor - apply_rate(amount, basis_points, mode).minor,
        &amount.currency,
    )
}

pub fn fune_vector(args: &[Value]) -> Value {
    money_to_value(&apply_rate(
        &money_from_value(&args[0]),
        args[1].as_i64(),
        args[2].as_str(),
    ))
}

Install

fune build

With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and its 2 dependencies, pins them in fune.lock, downloads only the Rust 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. A crate’s build.rs runs it before every compile. Or pin a range in fune.project and build in one step:

fune add money.apply-rate
Download for Rust money.apply-rate-1.0.1-rust.fune · 6,862 bytes sha256 f2a2a0a4110791d8ff5514066460b1f101cbc970332bf849da09d374de01a1c2

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

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