Functional Weave
Code in Rust

retail.price-rounding@1.0.0

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);
}