Functional Weave
Code in TypeScript

fleet.fuel-cost

Fuel cost of a trip from distance, fuel economy (UK or US mpg, or L/100km) and price per litre, exactly.

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

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

What it does

The fuel a trip burns and what it costs, from the distance, the vehicle's fuel economy and the pump price per litre. `fuelCost("100", "mi", "50", "mpg-uk", 1459, "GBP", "half-up")` is 9.092 litres costing 13.27.

## Inputs

For example

  • fuelCost(100, mi, 50, mpg-uk, 1,459, GBP, half-up) → litres 9.092, cost £13.27 100 miles at 50 mpg (UK) and 145.9p: 9.09218 litres, 1326.549p
  • fuelCost(100, km, 5.5, l-per-100km, 1,459, GBP, half-up) → litres 5.500, cost £8.02 100 km at 5.5 L/100km and 145.9p: 5.5 litres, 802.45p rounds down under half-up
  • fuelCost(100, km, 5.5, l-per-100km, 1,459, GBP, up) → litres 5.500, cost £8.03 the same trip rounded up

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 fuelCost(distance: string, distanceUnit: DistanceUnit, economy: string, economyUnit: EconomyUnit, pricePerLitreTenths: number, currency: string, mode: RoundingMode): FuelCost
distancestringplain decimal text, 0 or more: "250", "12.5"
distanceUnitDistanceUnitmi or km
economystringplain decimal text, above 0: "48.7" mpg or "5.9" L/100km
economyUnitEconomyUnitmpg-uk (imperial gallon), mpg-us (US gallon) or l-per-100km
pricePerLitreTenthsintpump price in tenths of a minor unit, as pumps show it: 1459 = 145.9p
currencystringISO 4217 code of the price
modeRoundingModehow the exact cost is rounded to a minor unit
returnsFuelCost

The types it declares, generated into your project

export type DistanceUnit = "mi" | "km";

export type EconomyUnit = "mpg-uk" | "mpg-us" | "l-per-100km";

/** The fuel a trip burns and what it costs. */
export interface FuelCost {
  /** exact litres rounded half-up to 3 decimal places, for display: "9.092" */
  readonly litres: string;
  /** from the exact litres, not the rounded ones */
  readonly cost: Money;
}

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

import { fuelCost } from "#fune/fleet.fuel-cost@^1";
impl/typescript.ts · 81 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.

import { type RoundingMode } from "./math_round_div.ts";  ← from math.round-div ^1.0.0 · built alongside by fune
import { roundDivBig } from "./math_round_div_big.ts";  ← from math.round-div-big ^1.0.0 · built alongside by fune
import { type Money, money } from "./money_amount.ts";  ← from money.amount ^1.0.0 · built alongside by fune
import { convertUnits } from "./units_convert.ts";  ← from units.convert ^1.0.0 · built alongside by fune
import { type DistanceUnit, type EconomyUnit, type FuelCost } from "./fleet_fuel_cost_types.ts";

const DECIMAL = /^[0-9]+(\.[0-9]+)?$/;
const MAX_SAFE = 9007199254740991n;

/** A plain decimal as an exact fraction: [digits without the point, power of ten]. */
function decimal(name: string, text: string): [bigint, bigint] {
  if (typeof text !== "string" || !DECIMAL.test(text)) {
    throw new RangeError(`${name} must be a plain decimal of 0 or more, received "${text}"`);
  }
  const point = text.indexOf(".");
  const places = point < 0 ? 0 : text.length - point - 1;
  return [BigInt(text.replace(".", "")), 10n ** BigInt(places)];
}

/**
 * The fuel a trip uses and what it costs.
 *
 * Everything stays an exact fraction until the one rounding step: litres come
 * from the distance and economy, and the cost from those exact litres. Rounding
 * the litres first, or using 4.546 for the imperial gallon, is a penny out
 * often enough to break a reconciliation. The gallon and the mile come from
 * units.convert, which defines them exactly (4.54609 L, 3.785411784 L,
 * 1.609344 km).
 */
export function fuelCost(
  distance: string,
  distanceUnit: DistanceUnit,
  economy: string,
  economyUnit: EconomyUnit,
  pricePerLitreTenths: number,
  currency: string,
  mode: RoundingMode
): FuelCost {
  if (distanceUnit !== "mi" && distanceUnit !== "km") {
    throw new RangeError(`unknown distance unit "${distanceUnit}"`);
  }
  if (economyUnit !== "mpg-uk" && economyUnit !== "mpg-us" && economyUnit !== "l-per-100km") {
    throw new RangeError(`unknown economy unit "${economyUnit}"`);
  }
  const [dNum, dDen] = decimal("distance", distance);
  const [eNum, eDen] = decimal("economy", economy);
  if (eNum === 0n) {
    throw new RangeError("economy must be greater than zero");
  }
  if (!Number.isInteger(pricePerLitreTenths)) {
    throw new TypeError(`pricePerLitreTenths must be an integer, received ${pricePerLitreTenths}`);
  }
  if (pricePerLitreTenths < 0) {
    throw new RangeError(`pricePerLitreTenths must not be negative, received ${pricePerLitreTenths}`);
  }
  money(0, currency);

  const [kmNum, kmDen] = decimal("mile", convertUnits("1", "mi", "km", 12));
  let lNum: bigint;
  let lDen: bigint;
  if (economyUnit === "l-per-100km") {
    // litres = km x (L/100km) / 100
    lNum = dNum * eNum * (distanceUnit === "mi" ? kmNum : 1n);
    lDen = dDen * eDen * 100n * (distanceUnit === "mi" ? kmDen : 1n);
  } else {
    // litres = miles / mpg x litres per gallon
    const [gNum, gDen] = decimal("gallon", convertUnits("1", economyUnit === "mpg-uk" ? "gal_imp" : "gal_us", "L", 12));
    lNum = dNum * eDen * gNum * (distanceUnit === "km" ? kmDen : 1n);
    lDen = dDen * eNum * gDen * (distanceUnit === "km" ? kmNum : 1n);
  }

  const cost = BigInt(roundDivBig((lNum * BigInt(pricePerLitreTenths)).toString(), (lDen * 10n).toString(), mode));
  if (cost > MAX_SAFE) {
    throw new RangeError("the fuel cost exceeds 2^53 - 1 minor units");
  }
  const thousandths = BigInt(roundDivBig((lNum * 1000n).toString(), lDen.toString(), "half-up"));
  const whole = (thousandths / 1000n).toString();
  const frac = (thousandths % 1000n).toString().padStart(3, "0");
  const result: Money = money(Number(cost), currency);
  return { litres: `${whole}.${frac}`, cost: result };
}

Install

fune build

With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and its 5 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 fleet.fuel-cost
Download for TypeScript fleet.fuel-cost-1.0.0-typescript.fune · 13,700 bytes sha256 22c723a4acc0e8adba879eda3b327e639b0778eda14b3174b9ca9d0b977c7b22

The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./fleet.fuel-cost-1.0.0-typescript.fune, or fetch it from a terminal with fune pull fleet.fuel-cost@1.0.0:typescript.

The whole function, every language, is one file too: fleet.fuel-cost-1.0.0.fune, 22,193 bytes, sha256 5022019ff35116bcc4305837d21b577312e7a9eb682e807d301f46e7f00b5dc3. 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 fleet.fuel-cost

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

// fune: after fleet.fuel-cost

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.big-integer in fleet.fuel-cost
// fune: replace math.round-div in fleet.fuel-cost
// fune: replace math.round-div-big in fleet.fuel-cost
// fune: replace money.amount in fleet.fuel-cost
// fune: replace units.convert in fleet.fuel-cost

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 fleet.fuel-cost --steps.

// fune: step fleet.fuel-cost 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
100 miles at 50 mpg (UK) and 145.9p: 9.09218 litres, 1326.549p 100, mi, 50, mpg-uk, 1,459, GBP, half-up → litres 9.092, cost £13.27
100 km at 5.5 L/100km and 145.9p: 5.5 litres, 802.45p rounds down under half-up 100, km, 5.5, l-per-100km, 1,459, GBP, half-up → litres 5.500, cost £8.02
the same trip rounded up 100, km, 5.5, l-per-100km, 1,459, GBP, up → litres 5.500, cost £8.03
100 miles at 5.5 L/100km: 160.9344 km, 8.851392 litres 100, mi, 5.5, l-per-100km, 1,459, GBP, half-up → litres 8.851, cost £12.91
100 km at 50 mpg (UK): 454.609 / 80.4672 = 5.6496... litres 100, km, 50, mpg-uk, 1,459, GBP, half-up → litres 5.650, cost £8.24
100 miles at 30 mpg (US): the US gallon is 3.785411784 litres 100, mi, 30, mpg-us, 1,459, GBP, half-up → litres 12.618, cost £18.41
one mile at 1 mpg (UK) and 81.5p is 370.506335p: rounding to 4.546 litres first would give 370 1, mi, 1, mpg-uk, 815, GBP, half-up → litres 4.546, cost £3.71
an exact half penny: 1 litre at 100.5p, half-up 100, km, 1, l-per-100km, 1,005, GBP, half-up → litres 1.000, cost £1.01
an exact half penny, half-even keeps the even 100 100, km, 1, l-per-100km, 1,005, GBP, half-even → litres 1.000, cost £1.00
an exact half penny, down 100, km, 1, l-per-100km, 1,005, GBP, down → litres 1.000, cost £1.00
Show the other 14 tests
CaseArgumentsExpected
no distance costs nothing 0, mi, 45, mpg-uk, 1,459, GBP, half-up → litres 0.000, cost £0.00
free fuel costs nothing 250, mi, 45, mpg-uk, 0, GBP, half-up → litres 25.256, cost £0.00
euro cents work the same way: 12.5 km at 6.4 L/100km and 1.799 EUR a litre 12.5, km, 6.4, l-per-100km, 1,799, EUR, half-up → litres 0.800, cost €1.44
an economy of zero is an error 100, mi, 0, mpg-uk, 1,459, GBP, half-up → error: economy must be greater than zero
a negative distance is an error -5, mi, 50, mpg-uk, 1,459, GBP, half-up → error: distance must be a plain decimal of 0 or more
an exponent is not a plain decimal 1e3, km, 50, mpg-uk, 1,459, GBP, half-up → error: distance must be a plain decimal of 0 or more
a trailing point is not a plain decimal 50, km, 6., l-per-100km, 1,459, GBP, half-up → error: economy must be a plain decimal of 0 or more
an unknown distance unit is an error 100, yd, 50, mpg-uk, 1,459, GBP, half-up → error: unknown distance unit
mpg without saying which gallon is an error 100, mi, 50, mpg, 1,459, GBP, half-up → error: unknown economy unit
a negative price is an error 100, mi, 50, mpg-uk, -1, GBP, half-up → error: pricePerLitreTenths must not be negative
a fractional price must be given in tenths 100, mi, 50, mpg-uk, 145.9, GBP, half-up → error: pricePerLitreTenths must be an integer
a lower-case currency is an error 100, mi, 50, mpg-uk, 1,459, gbp, half-up → error: is not an uppercase ISO 4217 currency code
an unknown rounding mode is an error 100, mi, 50, mpg-uk, 1,459, GBP, nearest → error: unknown rounding mode
a cost beyond 2^53 - 1 minor units is refused 999999999999999999, mi, 1, mpg-uk, 99,999, GBP, half-up → error: the fuel cost exceeds 2^53 - 1 minor units

More from the author

- `distance` and `economy` are plain decimal text ("250", "48.7"), so a distance read off an odometer or a WLTP figure goes in exactly. - `economyUnit` names the gallon. UK figures are miles per **imperial** gallon (4.54609 L); US figures are miles per US gallon (3.785411784 L), about 20% smaller. Plain `mpg` is refused because the two are easy to mix up. Or give litres per 100 km. - `pricePerLitreTenths` is the price as a pump shows it, to a tenth of a penny: 145.9p is `1459`. A price in whole pence is just `1450` and so on.

## Exactness

The litres are an exact fraction of the inputs. The mile (1.609344 km) and both gallons come from `units.convert`, which defines them exactly. The cost is litres x price, rounded once to a minor unit with the mode you choose. The litres in the result are rounded half-up to 3 places for display only: the cost never uses them. That matters: one mile at 1 mpg (UK) and 81.5p is 370.506335p, which is 371p; rounding the litres to 4.546 first (or using the common approximation 4.546 L per gallon) gives 370p.

The product is held in big integers (`math.round-div-big`), so long distances with many decimal places cannot overflow; a cost beyond 2^53 - 1 minor units is refused because JavaScript could not represent it.

## Edges

Zero distance and a zero price cost nothing. A zero economy, a negative or malformed number, an unknown unit, a fractional or negative price, a bad currency code and an unknown mode are errors.

Files

PathBytes
README.md1,722
impl/python.py3,263
impl/rust.rs4,851
impl/typescript.ts3,523
vectors.json4,570