Functional Weave
Code in Python

text.integer-to-words

Spell out a whole number in British English words, with "and" the way a cheque writes it.

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

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

What it does

Spells out a whole number in British English: 1234 is "one thousand two hundred and thirty-four". It is the integer half of cheque wording; the money form, with pounds and pence, is `text.number-to-words`, which builds on this.

## The rules

For example

  • integer_to_words(0) → zero zero is a word, not an empty string
  • integer_to_words(7) → seven a single digit
  • integer_to_words(13) → thirteen the teens are their own 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.

def integer_to_words(value: int) -> str
valueinta whole number; magnitude at most 9007199254740991
returnsstringlower case, hyphenated tens, "minus" for negatives

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

from fune.text.integer_to_words import integer_to_words  # text.integer-to-words@^1
impl/python.py · 74 lines · open · raw
from typing import Any, List

#: Largest integer a JavaScript number holds exactly; beyond it the three
#: languages disagree about what number was passed.
MAX_MAGNITUDE = 9007199254740991

_ONES = [
    "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
    "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen",
    "seventeen", "eighteen", "nineteen",
]
_TENS = ["", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"]

#: Short scale, as used in the UK since 1974: a billion is 10^9.
_SCALES = ["", "thousand", "million", "billion", "trillion", "quadrillion"]


def _below_hundred(n: int) -> str:
    if n < 20:
        return _ONES[n]
    unit = n % 10
    return _TENS[n // 10] if unit == 0 else "%s-%s" % (_TENS[n // 10], _ONES[unit])


def _group(n: int) -> str:
    """One group of three digits, 1 to 999."""
    hundreds, rest = divmod(n, 100)
    if hundreds == 0:
        return _below_hundred(rest)
    head = "%s hundred" % _ONES[hundreds]
    return head if rest == 0 else "%s and %s" % (head, _below_hundred(rest))


def _whole(value: Any) -> bool:
    # bool is an int in Python; True is not the number one here.
    return isinstance(value, int) and not isinstance(value, bool)


def integer_to_words(value: int) -> str:
    """Spell out a whole number in British English words, cheque style:
    1234 is "one thousand two hundred and thirty-four".
    """
    if not _whole(value):
        raise TypeError("integerToWords needs a whole number, received %r" % (value,))
    if value > MAX_MAGNITUDE or value < -MAX_MAGNITUDE:
        raise ValueError(
            "%d is out of range: integerToWords handles magnitudes up to %d" % (value, MAX_MAGNITUDE)
        )
    if value == 0:
        return "zero"

    # Groups of three, least significant first.
    groups: List[int] = []
    remaining = abs(value)
    while remaining > 0:
        groups.append(remaining % 1000)
        remaining //= 1000

    words: List[str] = []
    for i in range(len(groups) - 1, -1, -1):
        g = groups[i]
        if g == 0:
            continue
        text = _group(g)
        if i > 0:
            text += " " + _SCALES[i]
        elif len(groups) > 1 and g < 100:
            # British usage: "one thousand and one", "one million and fifty".
            # Only the final group gets it, and only without hundreds of its own.
            text = "and " + text
        words.append(text)

    body = " ".join(words)
    return "minus " + body if value < 0 else body

Install

fune build

With that line in your source, in a Python project (language python in fune.project), fune build resolves it and nothing else, pins them in fune.lock, downloads only the Python 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.integer-to-words
Download for Python text.integer-to-words-1.0.0-python.fune · 8,688 bytes sha256 5ea65d30fb63dc34e0b6b7f040d90238d7abce29175ff0fb49d26377ad17a49c

The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./text.integer-to-words-1.0.0-python.fune, or fetch it from a terminal with fune pull text.integer-to-words@1.0.0:python.

The whole function, every language, is one file too: text.integer-to-words-1.0.0.fune, 14,665 bytes, sha256 10077b87722a7d653cc638e87d36928c6c58749c0d5e58c5d408a21f496ad8b9. 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.integer-to-words

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

# fune: after text.integer-to-words

replace — it requires no other capability, so there is no dependency to replace.

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.integer-to-words --steps.

# fune: step text.integer-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.

CaseArgumentsExpected
zero is a word, not an empty string 0 → zero
a single digit 7 → seven
the teens are their own words 13 → thirteen
a round ten has no hyphen 20 → twenty
tens and units are hyphenated 21 → twenty-one
the largest two-digit number 99 → ninety-nine
a round hundred has no and 100 → one hundred
British English puts and after the hundred 101 → one hundred and one
and before a round ten 110 → one hundred and ten
the largest three-digit number 999 → nine hundred and ninety-nine
Show the other 15 tests
CaseArgumentsExpected
a round thousand 1,000 → one thousand
and before a final group under a hundred 1,001 → one thousand and one
and before a final round ten 1,010 → one thousand and ten
no and before a final group of a hundred or more 1,100 → one thousand one hundred
the cheque example 1,234 → one thousand two hundred and thirty-four
a hyphenated number of thousands 21,000 → twenty-one thousand
a hundred thousand has no and 100,000 → one hundred thousand
and reaches across empty groups to the final one 1,000,001 → one million and one
a middle group does not get an and 2,005,000 → two million five thousand
a billion is the short scale, ten to the ninth 1,000,000,000 → one billion
negative numbers read as minus -42 → minus forty-two
the largest supported magnitude 9,007,199,254,740,991 → nine quadrillion seven trillion one hundred and ninety-nine billion two hundred and fifty-four million seven hundred and forty thousand nine hundred and ninety-one
the most negative supported value -9,007,199,254,740,991 → minus nine quadrillion seven trillion one hundred and ninety-nine billion two hundred and fifty-four million seven hundred and forty thousand nine hundred and ninety-one
one past the safe integer range is refused 9,007,199,254,740,992 → error: out of range
a fraction is refused 1.5 → error: whole number

More from the author

- Numbers from 21 to 99 are hyphenated: "forty-two". - "and" joins a hundred to what follows it inside each group of three: "one hundred and one", "nine hundred and ninety-nine". - "and" also goes before the final group when that group is under a hundred and something larger precedes it: "one thousand and one", "one million and fifty". A final group of a hundred or more does not get one ("one thousand one hundred"), and nor do the middle groups ("two million five thousand"). That is the usual British cheque convention; American English leaves every "and" out, and this capability does not offer that form. - No commas are written between the groups. - Scales are the short scale used in the UK since 1974: thousand, million, billion (10^9), trillion (10^12), quadrillion (10^15). - Zero is "zero"; negative numbers get "minus" in front. - Everything is lower case. Capitalise the first letter yourself if the sentence needs it.

## Limits

The magnitude is capped at 9007199254740991 (2^53 - 1), the largest integer a JavaScript number holds exactly. Above it the three languages would disagree about what number was passed, so it is an error rather than a guess. A fractional number is an error too: this is for whole numbers, and money goes through `text.number-to-words` in integer minor units.

Files

PathBytes
README.md1,588
impl/python.py2,549
impl/rust.rs3,192
impl/typescript.ts2,407
vectors.json2,723