Functional Weave
Code in Rust

banking.cheque-amount-words

The words and figures to write on a cheque: "One hundred and twenty pounds and fifty pence only", "£120.50".

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

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

What it does

The two amount lines of a cheque: the words ("One thousand two hundred and thirty-four pounds and fifty-six pence only") and the figures ("£1,234.56").

It does not spell numbers itself. The words are `text.number-to-words` (`amountInWords`), so the British "and" rules, singular "one penny" and the supported currencies are exactly that capability's; the figures are `money.format`. What this adds is the cheque convention around them.

For example

  • cheque_amount_words(£1,234.56, words) → words One thousand two hundred and thirty-four pounds and fifty-six pence only, figures £1,234.56 pounds and pence in words
  • cheque_amount_words(£120.50, figures) → words One hundred and twenty pounds and 50p only, figures £120.50 pounds and pence as figures
  • cheque_amount_words(£10.00, words) → words Ten pounds only, figures £10.00 whole pounds leave the pence out in words

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 cheque_amount_words(amount: &Money, pence_style: &str) -> ChequeWording
amountMoneymore than zero; GBP, EUR or USD
pence_styleChequePenceStylewords spells the pence out; figures writes them as "50p"
returnsChequeWordingthe words line and the figures box

The types it declares, generated into your project

// ChequePenceStyle is a string in Rust, one of: "words", "figures".
// Parameters take it as &str and results hold it as String.

/// What goes on the two amount lines of a cheque.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChequeWording {
    /// capitalised, ending "only"
    pub words: String,
    /// e.g. "£1,234.56"
    pub figures: String,
}

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

fune!(banking.cheque-amount-words@^1);  // then call cheque_amount_words(…)
impl/rust.rs · 58 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::money_amount::{money, money_from_value, Money};  ← from money.amount ^1.0.0 · built alongside by fune
use super::money_format::format_money;  ← from money.format ^1.0.0 · built alongside by fune
use super::text_number_to_words::amount_in_words;  ← from text.number-to-words ^1.0.0 · built alongside by fune

/// The abbreviation written after pence as figures: 50p, 50c.
fn minor_suffix(currency: &str) -> &'static str {
    if currency == "GBP" {
        "p"
    } else {
        "c"
    }
}

/// The amount lines of a cheque. The words come from text.number-to-words,
/// capitalised and closed with "only" so nothing can be added after them; the
/// figures come from money.format.
///
/// # Panics
/// Panics on an amount of zero or less, an unknown pence style, or a currency
/// text.number-to-words has no words for.
pub fn cheque_amount_words(amount: &Money, pence_style: &str) -> ChequeWording {
    if amount.minor <= 0 {
        panic!("a cheque amount must be greater than zero, received {}", amount.minor);
    }
    let body = match pence_style {
        "words" => amount_in_words(amount),
        "figures" => {
            // The pounds are always in words, even when there are none, so the
            // line never starts with a figure that could be altered.
            let major = amount.minor / 100;
            let minor = amount.minor % 100;
            let mut text = amount_in_words(&money(major * 100, &amount.currency));
            if minor > 0 {
                text.push_str(&format!(" and {}{}", minor, minor_suffix(&amount.currency)));
            }
            text
        }
        other => panic!("unknown pence style \"{}\": expected words or figures", other),
    };
    let mut chars = body.chars();
    let words = match chars.next() {
        Some(first) => format!("{}{} only", first.to_uppercase(), chars.as_str()),
        None => String::from("only"),
    };
    ChequeWording {
        words,
        figures: format_money(amount),
    }
}

pub fn cheque_wording_to_value(w: &ChequeWording) -> Value {
    Value::obj(vec![("words", Value::str(&w.words)), ("figures", Value::str(&w.figures))])
}

pub fn fune_vector(args: &[Value]) -> Value {
    cheque_wording_to_value(&cheque_amount_words(&money_from_value(&args[0]), args[1].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 3 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 banking.cheque-amount-words
Download for Rust banking.cheque-amount-words-1.0.0-rust.fune · 11,178 bytes sha256 d2d612d3e99f4d8b10f8bbca0f2c34bab15a09f72e41f27793d3fe609fe87480

The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./banking.cheque-amount-words-1.0.0-rust.fune, or fetch it from a terminal with fune pull banking.cheque-amount-words@1.0.0:rust.

The whole function, every language, is one file too: banking.cheque-amount-words-1.0.0.fune, 14,328 bytes, sha256 80d0e873ffe7b7ee2442c5a529db085afa8ab56d9a5f17e5a870ca935d0a7a10. 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 banking.cheque-amount-words

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

// fune: after banking.cheque-amount-words

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 money.amount in banking.cheque-amount-words
// fune: replace money.format in banking.cheque-amount-words
// fune: replace text.number-to-words in banking.cheque-amount-words

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 banking.cheque-amount-words --steps.

// fune: step banking.cheque-amount-words 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
pounds and pence in words £1,234.56, words → words One thousand two hundred and thirty-four pounds and fifty-six pence only, figures £1,234.56
pounds and pence as figures £120.50, figures → words One hundred and twenty pounds and 50p only, figures £120.50
whole pounds leave the pence out in words £10.00, words → words Ten pounds only, figures £10.00
whole pounds leave the pence out as figures £10.00, figures → words Ten pounds only, figures £10.00
single pence are not padded £100.05, figures → words One hundred pounds and 5p only, figures £100.05
one pound and one penny £1.01, words → words One pound and one penny only, figures £1.01
under a pound in words £0.50, words → words Fifty pence only, figures £0.50
under a pound as figures still writes the pounds in words £0.50, figures → words Zero pounds and 50p only, figures £0.50
the smallest cheque £0.01, words → words One penny only, figures £0.01
British and after hundreds £101.00, words → words One hundred and one pounds only, figures £101.00
Show the other 8 tests
CaseArgumentsExpected
a million £1,000,000.00, words → words One million pounds only, figures £1,000,000.00
euros €25.75, words → words Twenty-five euros and seventy-five cents only, figures €25.75
euro cents as figures €25.75, figures → words Twenty-five euros and 75c only, figures €25.75
dollars $1.99, figures → words One dollar and 99c only, figures $1.99
a zero cheque is an error £0.00, words → error: a cheque amount must be greater than zero
a negative cheque is an error -£5.00, words → error: a cheque amount must be greater than zero
a currency without words is an error ¥500, words → error: no words for currency "JPY"
an unknown pence style is an error £5.00, roman → error: unknown pence style

More from the author

## Conventions

- The words line starts with a capital and ends with "only", the traditional guard against words being added after the amount. - `penceStyle` `words` spells the pence out; `figures` writes them as a number with "p" (GBP) or "c" (EUR, USD): "One hundred and twenty pounds and 50p only". Many UK cheque writers and banks' own guidance use the figures style; both are valid. Single pence are not padded: "and 5p". - Whole amounts leave the pence out in both styles: "Ten pounds only". - In the figures style the pounds are always in words, even when there are none ("Zero pounds and 50p only"), so the line never starts with a figure that could be altered. In the words style an amount under a pound reads "Fifty pence only", as `text.number-to-words` writes it.

## Edge cases

- The amount must be greater than zero: a cheque cannot be written for nothing or for a negative sum. (`text.number-to-words` accepts negatives for credit notes; this does not.) - GBP, EUR and USD only, because those are the currencies `text.number-to-words` has unit names for; anything else fails with its error ("no words for currency"). - US checks conventionally write cents as a fraction ("and 99/100 dollars"); that style is not offered.

Files

PathBytes
README.md1,731
impl/python.py1,429
impl/rust.rs2,157
impl/typescript.ts1,567
vectors.json4,341