text.number-to-words
Write a money amount in words for a cheque: "one thousand two hundred and thirty-four pounds and fifty-six pence".
1.0.0 · published 2026-10-03 by charlie · Anterra
Pinned by 16 tests, run in TypeScript, Python and Rust.
What it does
Writes a money amount the way it goes on a cheque or a remittance advice: 123456 pence is "one thousand two hundred and thirty-four pounds and fifty-six pence". The number words come from `text.integer-to-words`, so the British "and" rules are the same in both: "one hundred and one pounds", "one thousand and one pounds".
## Why it takes Money
For example
amountInWords(£1,234.56)→ one thousand two hundred and thirty-four pounds and fifty-six pence the cheque exampleamountInWords(£1.00)→ one pound one pound is singularamountInWords(£1.01)→ one pound and one penny one penny is singular, and not one pence
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 amountInWords(amount: Money): string
| amount | Money | GBP, EUR or USD, in integer minor units |
| returns | string | lower case British English, major units then minor units |
Your code names it in one line, in the file that uses it
import { amountInWords } from "#fune/text.number-to-words@^1";
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { type Money } from "./money_amount.ts"; ← from money.amount ^1.0.0 · built alongside by fune
import { integerToWords } from "./text_integer_to_words.ts"; ← from text.integer-to-words ^1.0.0 · built alongside by fune
interface UnitNames {
majorOne: string;
majorMany: string;
minorOne: string;
minorMany: string;
}
/**
* English unit names for the currencies this capability supports. All three
* have two decimal places, so the split below is by 100. A currency is not
* added here without its real unit names: they cannot be derived from a code.
*/
const UNITS: { [code: string]: UnitNames } = {
GBP: { majorOne: "pound", majorMany: "pounds", minorOne: "penny", minorMany: "pence" },
EUR: { majorOne: "euro", majorMany: "euros", minorOne: "cent", minorMany: "cents" },
USD: { majorOne: "dollar", majorMany: "dollars", minorOne: "cent", minorMany: "cents" },
};
/**
* Write a money amount in words, cheque style: 123456 GBP minor units is
* "one thousand two hundred and thirty-four pounds and fifty-six pence".
*/
export function amountInWords(amount: Money): string {
const units = Object.prototype.hasOwnProperty.call(UNITS, amount.currency) ? UNITS[amount.currency] : undefined;
if (units === undefined) {
throw new RangeError(`no words for currency "${amount.currency}"`);
}
// Validate the whole amount first, so an out-of-range value fails with the
// same message it would as a plain number.
integerToWords(amount.minor);
const magnitude = Math.abs(amount.minor);
const major = Math.floor(magnitude / 100);
const minor = magnitude % 100;
const parts: string[] = [];
if (major > 0 || minor === 0) {
parts.push(`${integerToWords(major)} ${major === 1 ? units.majorOne : units.majorMany}`);
}
if (minor > 0) {
parts.push(`${integerToWords(minor)} ${minor === 1 ? units.minorOne : units.minorMany}`);
}
const body = parts.join(" and ");
return amount.minor < 0 ? `minus ${body}` : body;
}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 text.number-to-words
The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./text.number-to-words-1.0.0-typescript.fune, or fetch it from a terminal with fune pull text.number-to-words@1.0.0:typescript.
The whole function, every language, is one file too: text.number-to-words-1.0.0.fune, 11,719 bytes, sha256 fdaf116aa82fea8fd199e36476f6a67f745c9cc3fc9eac5e96dae653996723b7. 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 text.number-to-words
after — your function gets the result and the arguments, and returns the final result.
// fune: after text.number-to-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 text.number-to-words
// fune: replace text.integer-to-words in text.number-to-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 text.number-to-words --steps.
// fune: step text.number-to-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.
| Case | Arguments | Expected | |
|---|---|---|---|
| the cheque example | £1,234.56 | → | one thousand two hundred and thirty-four pounds and fifty-six pence |
| one pound is singular | £1.00 | → | one pound |
| one penny is singular, and not one pence | £1.01 | → | one pound and one penny |
| a single penny on its own | £0.01 | → | one penny |
| under a pound leaves the pounds out | £0.50 | → | fifty pence |
| a whole amount leaves the pence out | £10.00 | → | ten pounds |
| zero is zero pounds | £0.00 | → | zero pounds |
| the British and inside the pounds | £101.00 | → | one hundred and one pounds |
| a hundred thousand pounds | £100,000.00 | → | one hundred thousand pounds |
| a million pounds and a penny | £1,000,000.01 | → | one million pounds and one penny |
Show the other 6 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| a credit note reads as minus | -£25.99 | → | minus twenty-five pounds and ninety-nine pence |
| minus a single penny | -£0.01 | → | minus one penny |
| one euro and fifty cents | €1.50 | → | one euro and fifty cents |
| euros are plural and one cent singular | €2,500.01 | → | two thousand five hundred euros and one cent |
| US dollars | $1.99 | → | one dollar and ninety-nine cents |
| a currency without listed unit names is an error, not a guess | ¥100 | → | error: no words for currency "JPY" |
More from the author
A cheque amount is money, and money in this registry is never a float. Taking `Money` (integer minor units) means 0.1 + 0.2 can never turn into "thirty pence and a bit", and the pence are always exactly what was paid.
## Wording
- Major units, then " and ", then minor units: "one pound and one penny". - Singular for exactly one: "one pound", "one penny", "one euro", "one cent", "one dollar". Otherwise plural: "pounds", "pence", "euros", "cents", "dollars". - A whole amount leaves the minor units out: "ten pounds", not "ten pounds and zero pence". An amount under one major unit leaves the major units out: "fifty pence". Zero is "zero pounds". - No "only" is added. Cheque writers traditionally end with "only" to stop words being added afterwards; append it yourself if your stationery wants it. - Negative amounts (credit notes) read as "minus ...". - Everything is lower case.
## Currencies
Only the three currencies whose English unit names are listed below are supported; any other currency is an error, not a guess, because the words for a currency's units are not derivable from its code.
| Code | Major (one / many) | Minor (one / many) | |------|--------------------|--------------------| | GBP | pound / pounds | penny / pence | | EUR | euro / euros | cent / cents | | USD | dollar / dollars | cent / cents |
All three have two decimal places. EU legislation spells the plural "euro" with no "s" in English; everyday British and Irish usage, and most invoices, say "euros", which is what this writes.
## Limits
The amount's minor units must be within 2^53 - 1 (the range all three languages agree on); `text.integer-to-words` rejects anything larger.
Files
| Path | Bytes |
|---|---|
| README.md | 2,088 |
| impl/python.py | 1,550 |
| impl/rust.rs | 1,962 |
| impl/typescript.ts | 1,858 |
| vectors.json | 2,163 |