Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
from .math_fractional_power import FIXED_SCALE, fractional_power_fixed ← from math.fractional-power ^1.0.0 · built alongside by fune
from .money_amount import Money ← from money.amount ^1.0.0 · built alongside by fune
#: The largest integer every language holds exactly (2^53 - 1).
_MAX_SAFE = 9007199254740991
def _whole_minor(amount: Money) -> int:
minor = amount.minor
if isinstance(minor, bool) or not isinstance(minor, int):
raise ValueError("amounts must be whole minor units, received %r" % (minor,))
return minor
def _check_years(name: str, value: int) -> None:
if isinstance(value, bool) or not isinstance(value, int) or value < 1 or value > 100000:
raise ValueError("%s must be a whole number from 1 to 100000, received %r" % (name, value))
def _round_half_away(n: int, d: int) -> int:
"""n / d rounded half away from zero; d > 0."""
magnitude = (abs(n) * 2 + d) // (2 * d)
return -magnitude if n < 0 else magnitude
def cagr(start_value: Money, end_value: Money, years_numerator: int, years_denominator: int) -> int:
"""Compound annual growth rate, (end / start)^(1 / years) - 1, in basis
points rounded half away from zero. Years are years_numerator /
years_denominator so no float ever enters the power."""
start = _whole_minor(start_value)
end = _whole_minor(end_value)
if start_value.currency != end_value.currency:
raise ValueError("currency mismatch: %s and %s" % (start_value.currency, end_value.currency))
_check_years("yearsNumerator", years_numerator)
_check_years("yearsDenominator", years_denominator)
if start <= 0:
raise ValueError("startValue must be greater than zero, received %d" % start)
if end < 0:
raise ValueError("endValue must not be negative, received %d" % end)
if end == 0:
return -10000
ratio = end * FIXED_SCALE // start
# Exponent 1 / years = years_denominator / years_numerator.
growth = fractional_power_fixed(ratio, years_denominator, years_numerator)
bp = _round_half_away((growth - FIXED_SCALE) * 10000, FIXED_SCALE)
if bp > _MAX_SAFE:
raise ValueError("the growth rate is too large to express in basis points")
return bp