Functional Weave
Code in Python

math.fractional-power

Raise a positive decimal to a fractional power, (1.199)^(31/365), identically in every language.

1.0.0 · published 2026-10-03 by charlie · Anterra

Pinned by 19 tests, run in TypeScript, Python and Rust.

What it does

Raises a positive decimal to a rational power, x^(p/q), and gets the same digits in TypeScript, Python and Rust. Finance needs this wherever a rate is compounded over part of a year: the consumer-credit APR and early-settlement formulas raise (1 + APR) to the power "days / 365", and a monthly factor is (1 + APR)^(1/12). Those are irrational numbers, so no exact answer exists; floating-point `Math.pow`, `**` and `powf` do not promise the same last digit on every platform, and a settlement figure must not depend on the platform.

## How it is computed

For example

  • fractional_power(1.1, 2, 1) → 1.210000000000 a whole power that is exact in decimal
  • fractional_power(2, 1, 2) → 1.414213562373 square root of two
  • fractional_power(1.199, 1, 12) → 1.015238935953 monthly factor of a 19.9% annual rate

The function

The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.

def fractional_power(base: str, exponent_numerator: int, exponent_denominator: int) -> str
basestringa positive decimal, at most 18 places: "1.199"
exponent_numeratorint-100000 to 100000; negative gives the reciprocal
exponent_denominatorint1 to 100000; the root taken
returnsstringthe result to 12 decimal places, half-up, e.g. "1.015521428741"

Your code names it in one line, in the file that uses it

from fune.math.fractional_power import fractional_power  # math.fractional-power@^1
impl/python.py · 112 lines · open · raw
import re
from typing import Optional

#: Every value is held as an integer count of 10^-18.
FIXED_SCALE = 10 ** 18

#: Intermediate values beyond 2^512 (about 10^136 after scaling) are refused.
_MAX_BITS = 512

_DECIMAL = re.compile(r"^[0-9]+(\.[0-9]{1,18})?$")


def _too_large(value: int) -> bool:
    return value.bit_length() > _MAX_BITS


def mul_fixed(a: int, b: int) -> int:
    """Product of two non-negative fixed-point values, floored."""
    return (a * b) // FIXED_SCALE


def pow_fixed_bounded(x: int, n: int, bound: Optional[int]) -> int:
    """x^n for a non-negative fixed-point x, by square-and-multiply from the
    lowest bit, flooring after every product.

    The order of operations is part of the contract: it is what makes
    TypeScript, Python and Rust agree to the last digit. Returns bound + 1 as
    soon as the result must exceed ``bound`` (x >= 1 only grows), so a
    bisection never builds an astronomically large number.
    """
    result = FIXED_SCALE
    base = x
    e = n
    while e > 0:
        if e % 2 == 1:
            result = mul_fixed(result, base)
            if bound is not None and result > bound and x >= FIXED_SCALE:
                return bound + 1
        e //= 2
        if e > 0:
            base = mul_fixed(base, base)
            if bound is not None and base > bound and x >= FIXED_SCALE:
                return bound + 1
            if _too_large(base):
                raise ValueError("fractional power result too large")
    if _too_large(result):
        raise ValueError("fractional power result too large")
    return result


def pow_fixed(x: int, n: int) -> int:
    """x^n in fixed point; n is a whole number of 0 or more."""
    return pow_fixed_bounded(x, n, None)


def root_fixed(x: int, q: int) -> int:
    """The q-th root of x in fixed point: the largest y with
    pow_fixed(y, q) <= x, found by bisection."""
    if q == 1:
        return x
    lo = 0
    # For x >= 1, (1 + (x - 1)/q)^q >= x (Bernoulli), so the root is below it.
    hi = FIXED_SCALE + (x - FIXED_SCALE) // q + 2 if x >= FIXED_SCALE else FIXED_SCALE + 1
    while hi - lo > 1:
        mid = (lo + hi) // 2
        if pow_fixed_bounded(mid, q, x) <= x:
            lo = mid
        else:
            hi = mid
    return lo


def fractional_power_fixed(x: int, p: int, q: int) -> int:
    """x^(p/q) in fixed point: the q-th root first, then the power, then the
    reciprocal if p < 0."""
    if isinstance(p, bool) or not isinstance(p, int) or p < -100000 or p > 100000:
        raise ValueError("exponentNumerator must be between -100000 and 100000, received %r" % (p,))
    if isinstance(q, bool) or not isinstance(q, int) or q < 1 or q > 100000:
        raise ValueError("exponentDenominator must be between 1 and 100000, received %r" % (q,))
    if x <= 0:
        raise ValueError("base must be greater than zero")
    root = root_fixed(x, q)
    if p >= 0:
        return pow_fixed(root, p)
    denominator = pow_fixed(root, -p)
    if denominator == 0:
        raise ValueError("fractional power result too large")
    result = (FIXED_SCALE * FIXED_SCALE) // denominator
    if _too_large(result):
        raise ValueError("fractional power result too large")
    return result


def parse_fixed(text: str) -> int:
    """Parse a non-negative decimal of at most 18 places into fixed point."""
    if not isinstance(text, str) or not _DECIMAL.fullmatch(text):
        raise ValueError('base must be a positive decimal with at most 18 places, received "%s"' % (text,))
    whole, _, fraction = text.partition(".")
    return int(whole) * FIXED_SCALE + int(fraction.ljust(18, "0"))


def fractional_power(base: str, exponent_numerator: int, exponent_denominator: int) -> str:
    """base^(exponent_numerator / exponent_denominator), to 12 decimal places.

    Computed in 18-place fixed point with a floor after every step, then
    rounded half-up to 12 places, so the last digit printed is right unless the
    true value sits within about 10^-15 of a rounding boundary.
    """
    x = parse_fixed(base)
    value = fractional_power_fixed(x, exponent_numerator, exponent_denominator)
    rounded = (value + 500000) // 1000000
    return "%d.%012d" % (rounded // 10 ** 12, rounded % 10 ** 12)

Install

fune build

With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 1 dependency, pins them in fune.lock, downloads only the Python package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:

fune add math.fractional-power
Download for Python math.fractional-power-1.0.0-python.fune · 11,373 bytes sha256 ced1ce111194c5b28d4551ffd37532f3d5d0f8a20b81e242a3763653653a4d87

The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./math.fractional-power-1.0.0-python.fune, or fetch it from a terminal with fune pull math.fractional-power@1.0.0:python.

The whole function, every language, is one file too: math.fractional-power-1.0.0.fune, 22,168 bytes, sha256 0066bd743f23291b72f2b4086147d1571e046c6c0b3849aa079fa868ff3e143c. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

# fune: before math.fractional-power

after — your function gets the result and the arguments, and returns the final result.

# fune: after math.fractional-power

replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.

# fune: replace math.big-integer in math.fractional-power

step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show math.fractional-power --steps.

# fune: step math.fractional-power after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

CaseArgumentsExpected
a whole power that is exact in decimal 1.1, 2, 1 → 1.210000000000
square root of two 2, 1, 2 → 1.414213562373
monthly factor of a 19.9% annual rate 1.199, 1, 12 → 1.015238935953
31 days of a 19.9% APR on a 365-day year 1.199, 31, 365 → 1.015533447820
a negative exponent discounts: 1/1.05^10 1.05, -10, 1 → 0.613913253541
three halves power 1.5, 3, 2 → 1.837117307087
a root of a number below one 0.5, 1, 3 → 0.793700525984
one to any power is one 1, 7, 365 → 1.000000000000
reciprocal of ten 10, -1, 1 → 0.100000000000
30 years of monthly compounding at 5.4% 1.0045, 360, 1 → 5.034760199014
Show the other 9 tests
CaseArgumentsExpected
a payday-style 1000% APR over 30 days 11, 30, 365 → 1.217850333327
a base with the full 18 decimal places 1.000000000000000001, 1,000, 1 → 1.000000000000
fraction not in lowest terms gives the same answer 1.199, 62, 730 → 1.015533447820
zero base is refused 0, 1, 2 → error: base must be greater than zero
a negative base is refused -1, 1, 1 → error: base must be a positive decimal with at most 18 places
more than 18 decimal places is refused 1.1234567890123456789, 1, 1 → error: base must be a positive decimal with at most 18 places
exponent denominator of zero 2, 1, 0 → error: exponentDenominator must be between 1 and 100000
exponent numerator out of range 2, 100,001, 1 → error: exponentNumerator must be between -100000 and 100000
a result beyond 2^512 is refused 10, 200, 1 → error: fractional power result too large

More from the author

Everything is an integer count of 10^-18 (18-place fixed point), and every step floors, in a fixed order, so the three languages agree bit for bit:

1. The q-th root: the largest y with y^q <= x, found by bisection. 2. The power: y^|p| by square-and-multiply from the lowest bit, flooring each product. 3. For a negative p, the reciprocal, floored.

The vectored function returns the result rounded half-up to 12 decimal places. Each floor loses under 10^-18, so the 12th place is right unless the true value lies within roughly 10^-15 of a rounding boundary. Exact powers that fit in 18 places come out exact: 1.1^2 is 1.21.

Other capabilities use the fixed-point pieces directly: `FIXED_SCALE`, `mulFixed`, `powFixed`, `powFixedBounded`, `rootFixed`, `fractionalPowerFixed` and `parseFixed` (snake_case in Python and Rust; the Rust versions take `math.big-integer`'s `BigInt`).

## Limits

- The base is a positive decimal with at most 18 places; zero and negatives are refused (a fractional power of a negative number is not real). - The exponent's numerator is -100000 to 100000 and its denominator 1 to 100000. The fraction need not be in lowest terms: 62/730 gives the same answer as 31/365 up to the floors, and in practice the same 12 places. - A value that would pass 2^512 in fixed point (about 10^136) is an error, not a slow computation.

Files

PathBytes
README.md1,944
impl/python.py4,248
impl/rust.rs6,009
impl/typescript.ts4,416
vectors.json2,908