Functional Weave
Code in Rust

fleet.fuel-cost@1.0.0

impl/python.py

3,263 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.

import re
from typing import Tuple

from .fleet_fuel_cost_types import DistanceUnit, EconomyUnit, FuelCost
from .math_round_div import RoundingMode  ← from math.round-div ^1.0.0 · built alongside by fune
from .math_round_div_big import round_div_big  ← from math.round-div-big ^1.0.0 · built alongside by fune
from .money_amount import money  ← from money.amount ^1.0.0 · built alongside by fune
from .units_convert import convert_units  ← from units.convert ^1.0.0 · built alongside by fune

DECIMAL = re.compile(r"^[0-9]+(\.[0-9]+)?$")
MAX_SAFE = 9007199254740991


def _decimal(name: str, text: str) -> Tuple[int, int]:
    """A plain decimal as an exact fraction: (digits without the point, power of ten)."""
    if not isinstance(text, str) or not DECIMAL.fullmatch(text):
        raise ValueError('%s must be a plain decimal of 0 or more, received "%s"' % (name, text))
    point = text.find(".")
    places = 0 if point < 0 else len(text) - point - 1
    return int(text.replace(".", "")), 10 ** places


def fuel_cost(
    distance: str,
    distance_unit: DistanceUnit,
    economy: str,
    economy_unit: EconomyUnit,
    price_per_litre_tenths: int,
    currency: str,
    mode: RoundingMode,
) -> FuelCost:
    """The fuel a trip uses and what it costs.

    Everything stays an exact fraction until the one rounding step: litres come
    from the distance and economy, and the cost from those exact litres.
    Rounding the litres first, or using 4.546 for the imperial gallon, is a
    penny out often enough to break a reconciliation. The gallon and the mile
    come from units.convert, which defines them exactly.
    """
    if distance_unit not in ("mi", "km"):
        raise ValueError('unknown distance unit "%s"' % (distance_unit,))
    if economy_unit not in ("mpg-uk", "mpg-us", "l-per-100km"):
        raise ValueError('unknown economy unit "%s"' % (economy_unit,))
    d_num, d_den = _decimal("distance", distance)
    e_num, e_den = _decimal("economy", economy)
    if e_num == 0:
        raise ValueError("economy must be greater than zero")
    if isinstance(price_per_litre_tenths, bool) or not isinstance(price_per_litre_tenths, int):
        raise TypeError("pricePerLitreTenths must be an integer, received %r" % (price_per_litre_tenths,))
    if price_per_litre_tenths < 0:
        raise ValueError("pricePerLitreTenths must not be negative, received %d" % (price_per_litre_tenths,))
    money(0, currency)

    km_num, km_den = _decimal("mile", convert_units("1", "mi", "km", 12))
    if economy_unit == "l-per-100km":
        # litres = km x (L/100km) / 100
        l_num = d_num * e_num * (km_num if distance_unit == "mi" else 1)
        l_den = d_den * e_den * 100 * (km_den if distance_unit == "mi" else 1)
    else:
        # litres = miles / mpg x litres per gallon
        g_num, g_den = _decimal("gallon", convert_units("1", "gal_imp" if economy_unit == "mpg-uk" else "gal_us", "L", 12))
        l_num = d_num * e_den * g_num * (km_den if distance_unit == "km" else 1)
        l_den = d_den * e_num * g_den * (km_num if distance_unit == "km" else 1)

    cost = int(round_div_big(str(l_num * price_per_litre_tenths), str(l_den * 10), mode))
    if cost > MAX_SAFE:
        raise ValueError("the fuel cost exceeds 2^53 - 1 minor units")
    thousandths = int(round_div_big(str(l_num * 1000), str(l_den), "half-up"))
    litres = "%d.%03d" % (thousandths // 1000, thousandths % 1000)
    return FuelCost(litres=litres, cost=money(cost, currency))