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
markup_price(£60.00, 40%, margin, half-up)→ £100.00 60.00 at a 40 percent margin is 100.00, as finance.margin saysmarkup_price(£100.00, 50%, markup, half-up)→ £150.00 100.00 at a 50 percent markup is 150.00markup_price(£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.
def markup_price(cost: Money, basis_points: int, basis: PricingBasis, mode: RoundingMode) -> Money
| cost | Money | what the item costs to buy or make; not negative |
| basis_points | int | the target: 4000 is a 40% margin or a 40% markup, as basis says |
| basis | PricingBasis | margin (profit over price) or markup (profit over cost) |
| mode | RoundingMode | how to round the price to a minor unit; use up to never fall short of a margin |
| returns | Money |
The type it declares, generated into your project
PricingBasis = Literal["margin", "markup"]
Your code names it in one line, in the file that uses it
from fune.finance.markup_price import markup_price # finance.markup-price@^1
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
from .finance_markup_price_types import PricingBasis
from .math_round_div import RoundingMode, round_div ← from math.round-div ^1.0.0 · built alongside by fune
from .money_amount import Money, money ← from money.amount ^1.0.0 · built alongside by fune
MAX_SAFE = 9007199254740991
def markup_price(cost: Money, basis_points: int, basis: PricingBasis, mode: RoundingMode) -> Money:
"""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.
"""
if isinstance(basis_points, bool) or not isinstance(basis_points, int):
raise TypeError("basis_points must be an integer, received %r" % (basis_points,))
if cost.minor < 0:
raise ValueError("cost must not be negative, received %d" % (cost.minor,))
if basis == "markup":
if basis_points < -10000:
raise ValueError("a markup of %d basis points gives a negative price" % (basis_points,))
numerator = cost.minor * (10000 + basis_points)
denominator = 10000
elif basis == "margin":
if basis_points >= 10000:
raise ValueError("a margin of %d basis points has no finite price" % (basis_points,))
numerator = cost.minor * 10000
denominator = 10000 - basis_points
else:
raise ValueError('unknown pricing basis "%s"' % (basis,))
# Python would carry on, but TypeScript cannot, and all three must agree.
if numerator > MAX_SAFE:
raise ValueError("the price calculation exceeds 2^53 - 1")
return money(round_div(numerator, denominator, mode), cost.currency)Install
fune build
With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 2 dependencies, 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 finance.markup-price
The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./finance.markup-price-1.0.0-python.fune, or fetch it from a terminal with fune pull finance.markup-price@1.0.0:python.
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.
| Case | Arguments | Expected | |
|---|---|---|---|
| 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
| Case | Arguments | Expected | |
|---|---|---|---|
| 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
| Path | Bytes |
|---|---|
| README.md | 1,666 |
| impl/python.py | 1,599 |
| impl/rust.rs | 1,872 |
| impl/typescript.ts | 1,567 |
| vectors.json | 5,487 |