math.big-integer
Exact integer arithmetic of any size, on decimal strings, for powers and products that overflow 64 bits.
1.0.0 · published 2026-10-03 by charlie · Anterra
Pinned by 24 tests, run in TypeScript, Python and Rust.
What it does
Exact whole-number arithmetic with no size limit worth mentioning, for the calculations where 64 bits run out: a monthly loan compounds (1 + r) over 300 periods, and the exact fraction behind the payment has numerators thousands of digits long. Money and rates in this registry are integers, so exactness is available if the integers are allowed to be big.
Numbers travel as decimal strings ("-123", "0", "98765..."), because that is the one form all three languages read and write identically: a JavaScript number stops being exact at 2^53.
For example
calculate_big_integer(123456789012345678901234567890, add, 987654321098765432109876543210)→ 1111111110111111111011111111100 adds past 64 bitscalculate_big_integer(9223372036854775807, add, 1)→ 9223372036854775808 i64 max plus one does not wrapcalculate_big_integer(123456789012345678901234567890, subtract, 987654321098765432109876543210)→ -864197532086419753208641975320 subtracting a larger number goes negative
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 calculate_big_integer(a: str, op: BigIntegerOp, b: str) -> str
| a | string | a whole number in decimal: optional "-", digits, no leading zeros |
| op | BigIntegerOp | add, subtract, multiply, divide (towards zero), remainder (sign of a) or power |
| b | string | a whole number in decimal; for power, 0 or more |
| returns | string | the exact result in the same decimal form |
The type it declares, generated into your project
BigIntegerOp = Literal["add", "subtract", "multiply", "divide", "remainder", "power"]
Your code names it in one line, in the file that uses it
from fune.math.big_integer import calculate_big_integer # math.big-integer@^1
import re
from .math_big_integer_types import BigIntegerOp
_INTEGER = re.compile(r"^-?(0|[1-9][0-9]*)$")
#: The largest result, in bits, that power will build: about 19,700 decimal digits.
MAX_POWER_BITS = 65536
def parse_big_integer(text: str) -> int:
"""Parse the registry's decimal integer form. "-0", "+1", "01" and " 1" are refused."""
if not isinstance(text, str) or not _INTEGER.fullmatch(text) or text == "-0":
raise ValueError('not a whole number in decimal: "%s"' % (text,))
return int(text)
def _trunc_div(x: int, y: int) -> int:
q = abs(x) // abs(y)
return -q if (x < 0) != (y < 0) else q
def power_big_integer(base: int, exponent: int) -> int:
"""Raise to a power, refusing results beyond MAX_POWER_BITS before building them."""
if exponent < 0:
raise ValueError("exponent must not be negative")
magnitude = abs(base)
if magnitude >= 2 and magnitude.bit_length() * exponent > MAX_POWER_BITS:
raise ValueError("result too large: power is limited to %d bits" % (MAX_POWER_BITS,))
if magnitude <= 1:
if exponent == 0:
return 1
if base == -1:
return 1 if exponent % 2 == 0 else -1
return base
return base ** exponent
def calculate_big_integer(a: str, op: BigIntegerOp, b: str) -> str:
"""Exact integer arithmetic on decimal strings.
Division truncates towards zero and the remainder takes the sign of the
dividend, as in TypeScript and Rust. Python's own // floors, so it is not
used directly on signed values.
"""
x = parse_big_integer(a)
y = parse_big_integer(b)
if op == "add":
return str(x + y)
if op == "subtract":
return str(x - y)
if op == "multiply":
return str(x * y)
if op == "divide":
if y == 0:
raise ZeroDivisionError("division by zero")
return str(_trunc_div(x, y))
if op == "remainder":
if y == 0:
raise ZeroDivisionError("division by zero")
return str(x - y * _trunc_div(x, y))
if op == "power":
return str(power_big_integer(x, y))
raise ValueError('unknown operation "%s"' % (op,))Install
fune build
With that line in your source, in a Python project (language python in fune.project), fune build resolves it and nothing else, 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.big-integer
The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./math.big-integer-1.0.0-python.fune, or fetch it from a terminal with fune pull math.big-integer@1.0.0:python.
The whole function, every language, is one file too: math.big-integer-1.0.0.fune, 27,178 bytes, sha256 242eb37623145ad5d7b0d7b9063ea4d93bac3ec93828ccab3e7149d5c7ed9af3. 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.big-integer
after — your function gets the result and the arguments, and returns the final result.
# fune: after math.big-integer
replace — it requires no other capability, so there is no dependency to replace.
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.big-integer --steps.
# fune: step math.big-integer 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.
| Case | Arguments | Expected | |
|---|---|---|---|
| adds past 64 bits | 123456789012345678901234567890, add, 987654321098765432109876543210 | → | 1111111110111111111011111111100 |
| i64 max plus one does not wrap | 9223372036854775807, add, 1 | → | 9223372036854775808 |
| subtracting a larger number goes negative | 123456789012345678901234567890, subtract, 987654321098765432109876543210 | → | -864197532086419753208641975320 |
| 2^64 squared is 2^128 | 18446744073709551616, multiply, 18446744073709551616 | → | 340282366920938463463374607431768211456 |
| negative times negative is positive | -99999999999999999999, multiply, -3 | → | 299999999999999999997 |
| anything times zero is zero | -123456789012345678901234567890, multiply, 0 | → | 0 |
| divide truncates towards zero | -7, divide, 2 | → | -3 |
| remainder takes the sign of the dividend | -7, remainder, 2 | → | -1 |
| positive by negative divisor | 7, remainder, -2 | → | 1 |
| long division of large numbers | 1606938044258990275541962092341162602522202993782792835313721, divide, 717897987691852588770249 | → | 2238393297946874000179418290327143433 |
Show the other 14 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| remainder of large numbers | 1606938044258990275541962092341162602522202993782792835313721, remainder, 717897987691852588770249 | → | 249667313308346329188904 |
| power of two | 2, power, 100 | → | 1267650600228229401496703205376 |
| a negative base to an odd power | -3, power, 41 | → | -36472996377170786403 |
| zero to the zero is one | 0, power, 0 | → | 1 |
| minus one to a huge power | -1, power, 1000000001 | → | -1 |
| compound growth factor 1.0045^12, scaled | 10045, power, 12 | → | 1055356751950102607459752843054131873164306640625 |
| a malformed number is refused | 01, add, 1 | → | error: not a whole number in decimal |
| minus zero is refused | -0, add, 1 | → | error: not a whole number in decimal |
| a decimal point is refused | 1.5, add, 1 | → | error: not a whole number in decimal |
| division by zero | 1, divide, 0 | → | error: division by zero |
| remainder by zero | 1, remainder, 0 | → | error: division by zero |
| a negative exponent is refused | 2, power, -1 | → | error: exponent must not be negative |
| a power beyond the size limit is refused before it is built | 3, power, 50000 | → | error: result too large |
| an unknown operation | 1, modulo, 2 | → | error: unknown operation |
More from the author
## Operations
- `add`, `subtract`, `multiply`: exact. - `divide`: the quotient truncated towards zero, so -7 / 2 is -3. - `remainder`: takes the sign of the dividend, so -7 rem 2 is -1 and a = (a / b) × b + (a rem b) always holds. This is TypeScript's and Rust's rule; Python floors by default and is adjusted to agree. - `power`: the exponent is 0 or more; 0^0 is 1.
## Limits
`power` refuses a result that would exceed 65,536 bits (about 19,700 decimal digits) before building it, estimated as bits(base) × exponent, the same way in every language, so a slip of the exponent fails at once instead of hanging. The other operations have no limit beyond memory and time; the Rust side is schoolbook multiplication and shift-subtract division, which is quadratic.
## Why a capability, and what it exports
TypeScript (`bigint`) and Python (`int`) have arbitrary-precision integers built in. Rust's standard library does not, and the registry takes no crates, so the Rust module is a real implementation: it exports a `BigInt` type (`from_i64`, `from_i128`, `add`, `sub`, `mul`, `div_rem`, `pow`, `to_i64`, `to_i128`, `div_rem_small`, ordering and `Display`) that other capabilities build on, plus `parse_big_integer` and `power_big_integer`. TypeScript and Python export `parseBigInteger` / `parse_big_integer` and `powerBigInteger` / `power_big_integer` over their native integers.
Numbers must be in canonical form: an optional "-", then digits with no leading zero. "-0", "+1", "01", " 1" and "1.0" are errors, not guesses.
Files
| Path | Bytes |
|---|---|
| README.md | 2,100 |
| impl/python.py | 2,180 |
| impl/rust.rs | 13,187 |
| impl/typescript.ts | 2,388 |
| vectors.json | 4,026 |