Functional Weave
Code in Python

money.allocate

Split an amount across ratios without losing or inventing a single minor unit.

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

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

What it does

Splitting 10.00 three ways gives 3.34, 3.33, 3.33 - never 3.33 three times with a penny quietly lost, and never 3.34 three times with a penny invented.

The leftover is distributed by largest remainder, ties broken by position, so the same input always produces the same split in every language.

For example

  • allocate(£10.00, 1, 1, 1) → £3.34, £3.33, £3.33 ten pounds three ways keeps the penny
  • allocate(£5.00, 3, 7) → £1.50, £3.50 exact split needs no remainder
  • allocate(£100.00, 1, 1, 1, 1, 1, 1) → £16.67, £16.67, £16.67, £16.67, £16.66, £16.66 weighted split distributes by largest remainder

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 allocate(amount: Money, ratios: Sequence[int]) -> List[Money]
amountMoney
ratiosint[]relative weights; [1,1,1] is an even three-way split
returnsMoney[]

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

from fune.money.allocate import allocate  # money.allocate@^1
impl/python.py · 38 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.

from typing import List, Sequence

from .money_amount import Money, money  ← from money.amount ^1.0.0 · built alongside by fune


def allocate(amount: Money, ratios: Sequence[int]) -> List[Money]:
    """Split ``amount`` across ``ratios`` so the parts add back up to the whole.

    Splitting 10.00 three ways gives 3.34, 3.33, 3.33 - never 3.33 three times
    with a penny quietly lost. The leftover goes to the largest remainders,
    ties broken by position, so the split is stable and reproducible.
    """
    if len(ratios) == 0:
        raise ValueError("allocate needs at least one ratio")
    if any(isinstance(r, bool) or not isinstance(r, int) for r in ratios):
        raise TypeError("ratios must be integers")
    total = sum(ratios)
    if total == 0:
        raise ValueError("ratios must not sum to zero")

    bases = []
    remainders = []
    for ratio in ratios:
        numerator = amount.minor * ratio
        base = numerator // total
        bases.append(base)
        remainders.append(numerator - base * total)

    leftover = amount.minor - sum(bases)

    order = sorted(range(len(ratios)), key=lambda i: (-remainders[i], i))
    for index in order:
        if leftover <= 0:
            break
        bases[index] += 1
        leftover -= 1

    return [money(base, amount.currency) for base in bases]

Install

fune build

With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 1 dependency, 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 money.allocate
Download for Python money.allocate-1.0.1-python.fune · 7,136 bytes sha256 e540ec12e29a20ae82f5f9af5cc2701e6dcda1b33a0f85e9c7ae5e079c104c62

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

The whole function, every language, is one file too: money.allocate-1.0.1.fune, 10,802 bytes, sha256 9cce7c55a15100ba63fdb2177af7103771921fe17b05187a707c3473df1440ee. 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.allocate

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

# fune: after money.allocate

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 money.allocate

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.allocate --steps.

# fune: step money.allocate 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
ten pounds three ways keeps the penny £10.00, 1, 1, 1 → £3.34, £3.33, £3.33
exact split needs no remainder £5.00, 3, 7 → £1.50, £3.50
weighted split distributes by largest remainder £100.00, 1, 1, 1, 1, 1, 1 → £16.67, £16.67, £16.67, £16.67, £16.66, £16.66
credit notes split without inventing money -£10.00, 1, 1, 1 → -£3.33, -£3.33, -£3.34
single share takes everything £9.99, 1 → £9.99
zero ratios are an error £1.00, 0, 0 → error: ratios must not sum to zero
empty ratios are an error £1.00, → error: at least one ratio
the leftover penny goes to the largest remainder, not the first share £1.00, 1, 2 → £0.33, £0.67
one penny three ways: only the first share gets it £0.01, 1, 1, 1 → £0.01, £0.00, £0.00
two pennies three ways: ties go in order £0.02, 1, 1, 1 → £0.01, £0.01, £0.00
Show the other 7 tests
CaseArgumentsExpected
a zero ratio gets nothing £10.00, 1, 0, 1 → £5.00, £0.00, £5.00
a zero amount splits into zeros £0.00, 1, 2 → £0.00, £0.00
a weighted split with a remainder £10.01, 1, 2, 3, 4 → £1.00, £2.00, £3.00, £4.01
a weighted credit still adds back to the whole -£1.00, 1, 2 → -£0.33, -£0.67
zero-decimal currency ¥1,000, 1, 1, 1 → ¥334, ¥333, ¥333
the largest safe integer halves without losing a unit £90,071,992,547,409.91, 1, 1 → £45,035,996,273,704.96, £45,035,996,273,704.95
ratios that cancel out are an error £1.00, 1, -1 → error: ratios must not sum to zero

More from the author

1.0.1 adds tests; behaviour unchanged.

Files

PathBytes
README.md354
impl/python.py1,279
impl/rust.rs2,108
impl/typescript.ts1,414
vectors.json3,682