Functional Weave
Code in Python

retail.price-rounding@1.0.0

impl/python.py

1,557 bytes · the Python 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.

from .math_round_div import 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
from .retail_price_rounding_types import PriceDirection


def _whole(value: object) -> bool:
    return isinstance(value, int) and not isinstance(value, bool)


def round_price(price: Money, step: int, ending: int, direction: PriceDirection) -> Money:
    """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.
    """
    if not _whole(step) or step < 1:
        raise ValueError("step must be 1 or more, received %r" % (step,))
    if not _whole(ending) or ending < 0 or ending >= step:
        raise ValueError("ending must be from 0 to step - 1, received %r" % (ending,))
    if price.minor < 0:
        raise ValueError("price must not be negative, received %d" % price.minor)
    shifted = price.minor + ending
    if direction == "up":
        point = round_div(shifted, step, "up") * step - ending
    elif direction == "down":
        point = round_div(shifted, step, "down") * step - ending
        if point < 0:
            raise ValueError("no price point at or below %d" % price.minor)
    elif direction == "nearest":
        point = round_div(shifted, step, "half-up") * step - ending
        # Below the first point the only candidate is above.
        if point < 0:
            point = step - ending
    else:
        raise ValueError('unknown direction "%s"' % (direction,))
    return money(point, price.currency)