impl/typescript.ts
1,526 bytes · the TypeScript implementation · view raw
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { 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 PriceDirection } from "./retail_price_rounding_types.ts";
/**
* Snap a price to the points k x step - ending.
*
* Shifting by `ending` turns every point into a multiple of `step`, so the
* whole policy is one integer division with the right rounding mode.
*/
export function roundPrice(price: Money, step: number, ending: number, direction: PriceDirection): Money {
if (!Number.isInteger(step) || step < 1) {
throw new RangeError(`step must be 1 or more, received ${step}`);
}
if (!Number.isInteger(ending) || ending < 0 || ending >= step) {
throw new RangeError(`ending must be from 0 to step - 1, received ${ending}`);
}
if (price.minor < 0) {
throw new RangeError(`price must not be negative, received ${price.minor}`);
}
const shifted = price.minor + ending;
let point: number;
if (direction === "up") {
point = roundDiv(shifted, step, "up") * step - ending;
} else if (direction === "down") {
point = roundDiv(shifted, step, "down") * step - ending;
if (point < 0) throw new RangeError(`no price point at or below ${price.minor}`);
} else if (direction === "nearest") {
point = roundDiv(shifted, step, "half-up") * step - ending;
// Below the first point the only candidate is above.
if (point < 0) point = step - ending;
} else {
throw new RangeError(`unknown direction "${direction}"`);
}
return money(point, price.currency);
}