Functional Weave
Code in TypeScript

finance.markup-price

Selling price from a cost and a target margin or markup in basis points, with explicit rounding.

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

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

What it does

The inverse of `finance.margin`: given a cost and a target, the price.

- **markup** is profit over cost: `price = cost × (1 + markup)`. A 50% markup on 100.00 is 150.00. - **margin** is profit over price: `price = cost / (1 - margin)`. A 50% margin on 100.00 is 200.00, not 150.00.

For example

  • markupPrice(£60.00, 40%, margin, half-up) → £100.00 60.00 at a 40 percent margin is 100.00, as finance.margin says
  • markupPrice(£100.00, 50%, markup, half-up) → £150.00 100.00 at a 50 percent markup is 150.00
  • markupPrice(£100.00, 50%, margin, half-up) → £200.00 100.00 at a 50 percent margin is 200.00, not 150.00

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 markupPrice(cost: Money, basisPoints: number, basis: PricingBasis, mode: RoundingMode): Money
costMoneywhat the item costs to buy or make; not negative
basisPointsintthe target: 4000 is a 40% margin or a 40% markup, as basis says
basisPricingBasismargin (profit over price) or markup (profit over cost)
modeRoundingModehow to round the price to a minor unit; use up to never fall short of a margin
returnsMoney

The type it declares, generated into your project

export type PricingBasis = "margin" | "markup";

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

import { markupPrice } from "#fune/finance.markup-price@^1";
impl/typescript.ts · 41 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, roundDiv } from "./math_round_div.ts";  ← from math.round-div ^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 { type PricingBasis } from "./finance_markup_price_types.ts";

/**
 * The selling price that hits a target margin or markup on `cost`.
 *
 * Margin divides by the price and markup by the cost, so the same 5000 basis
 * points means 200.00 or 150.00 on a cost of 100.00; the basis is explicit.
 */
export function markupPrice(cost: Money, basisPoints: number, basis: PricingBasis, mode: RoundingMode): Money {
  if (!Number.isInteger(basisPoints)) {
    throw new TypeError(`basisPoints must be an integer, received ${basisPoints}`);
  }
  if (cost.minor < 0) {
    throw new RangeError(`cost must not be negative, received ${cost.minor}`);
  }

  let numerator: number;
  let denominator: number;
  if (basis === "markup") {
    if (basisPoints < -10000) {
      throw new RangeError(`a markup of ${basisPoints} basis points gives a negative price`);
    }
    numerator = cost.minor * (10000 + basisPoints);
    denominator = 10000;
  } else if (basis === "margin") {
    if (basisPoints >= 10000) {
      throw new RangeError(`a margin of ${basisPoints} basis points has no finite price`);
    }
    numerator = cost.minor * 10000;
    denominator = 10000 - basisPoints;
  } else {
    throw new RangeError(`unknown pricing basis "${basis}"`);
  }

  if (!Number.isSafeInteger(numerator)) {
    throw new RangeError("the price calculation exceeds 2^53 - 1");
  }
  return money(roundDiv(numerator, denominator, mode), cost.currency);
}

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 finance.markup-price
Download for TypeScript finance.markup-price-1.0.0-typescript.fune · 11,517 bytes sha256 35eddd3540f3800ed2abc51dffe31fde7d3f87e2d48695ed59821497ef7335b4

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

The whole function, every language, is one file too: finance.markup-price-1.0.0.fune, 15,136 bytes, sha256 bfaa16042b3d9d9018be2d19e2ead48b179a814facd04d7ea13796de6f55ac33. 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 finance.markup-price

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

// fune: after finance.markup-price

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.round-div in finance.markup-price
// fune: replace money.amount in finance.markup-price

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 finance.markup-price --steps.

// fune: step finance.markup-price 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
60.00 at a 40 percent margin is 100.00, as finance.margin says £60.00, 40%, margin, half-up → £100.00
100.00 at a 50 percent markup is 150.00 £100.00, 50%, markup, half-up → £150.00
100.00 at a 50 percent margin is 200.00, not 150.00 £100.00, 50%, margin, half-up → £200.00
6.99 at a 30.03 percent margin is 9.99, finance.margin backwards £6.99, 30.03%, margin, half-up → £9.99
6.99 at a 42.92 percent markup is 9.99, finance.margin backwards £6.99, 42.92%, markup, half-up → £9.99
10.00 at a 30 percent margin rounds half-up to 14.29 £10.00, 30%, margin, half-up → £14.29
rounding down misses a 30 percent margin: 14.28 is 29.97 percent £10.00, 30%, margin, down → £14.28
rounding up never falls short of the target £10.00, 30%, margin, up → £14.29
an exact half penny rounds to even: 1.5 becomes 2 £0.01, 50%, markup, half-even → £0.02
zero margin is the cost £43.21, 0%, margin, half-up → £43.21
Show the other 11 tests
CaseArgumentsExpected
zero markup is the cost £43.21, 0%, markup, half-up → £43.21
zero cost is a zero price £0.00, 40%, margin, half-up → £0.00
a negative markup is a discount below cost £100.00, -20%, markup, half-up → £80.00
a markup of -100 percent is a giveaway £5.00, -100%, markup, half-up → £0.00
a negative margin prices below cost, as finance.margin reports it £120.00, -20%, margin, half-up → £100.00
yen at a 20 percent margin ¥800, 20%, margin, half-up → ¥1,000
a 100 percent margin has no finite price £10.00, 100%, margin, half-up → error: has no finite price
a markup below -100 percent would be a negative price £10.00, -100.01%, markup, half-up → error: gives a negative price
a negative cost is an error -£10.00, 20%, markup, half-up → error: cost must not be negative
the basis has no default and must be named £10.00, 20%, profit, half-up → error: unknown pricing basis
a calculation beyond 2^53 - 1 is refused £9,007,199,254,741.00, 0%, margin, half-up → error: exceeds 2^53 - 1

More from the author

Confusing the two is the classic pricing mistake - a retailer aiming for a 40% margin who adds 40% to cost gets a 28.57% margin - so the basis is a required argument with no default.

The price is rounded once, to the currency's minor unit, with the `math.round-div` mode you pass. Rounding can land a fraction of a basis point either side of the target. Use `up` for a margin or markup target you must not fall below: 1,000 at a 30% margin is 1,428.57..., and `down` gives 1,428, which is a 29.97% margin, while `up` gives 1,429.

The results agree with `finance.margin`: several vectors here are that capability's vectors run backwards (60.00 at a 40% margin is 100.00; 6.99 at a 30.03% margin is 9.99; 120.00 at a -20% margin is 100.00), and feeding the price back into `finance.margin` returns the target to within its rounding.

Edges: a margin of 100% or more has no finite price and is an error. A negative margin or markup is a price below cost and is allowed (clearance, loss leaders); a markup of -100% is a price of zero, and below that the price would be negative, which is an error. A negative cost is an error. Zero cost gives a zero price on either basis. `cost × 10000` (or `cost × (10000 + markup)`) must stay within 2^53 - 1, which is costs up to about 900 million pounds; beyond that the calculation is refused rather than rounded.

Files

PathBytes
README.md1,666
impl/python.py1,599
impl/rust.rs1,872
impl/typescript.ts1,567
vectors.json5,487