Functional Weave
Code in TypeScript

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

  • calculateBigInteger(123456789012345678901234567890, add, 987654321098765432109876543210) → 1111111110111111111011111111100 adds past 64 bits
  • calculateBigInteger(9223372036854775807, add, 1) → 9223372036854775808 i64 max plus one does not wrap
  • calculateBigInteger(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.

export function calculateBigInteger(a: string, op: BigIntegerOp, b: string): string
astringa whole number in decimal: optional "-", digits, no leading zeros
opBigIntegerOpadd, subtract, multiply, divide (towards zero), remainder (sign of a) or power
bstringa whole number in decimal; for power, 0 or more
returnsstringthe exact result in the same decimal form

The type it declares, generated into your project

export type BigIntegerOp = "add" | "subtract" | "multiply" | "divide" | "remainder" | "power";

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

import { calculateBigInteger } from "#fune/math.big-integer@^1";
impl/typescript.ts · 67 lines · open · raw
import { type BigIntegerOp } from "./math_big_integer_types.ts";

const INTEGER = /^-?(0|[1-9][0-9]*)$/;

/** The largest result, in bits, that power will build: about 19,700 decimal digits. */
export const MAX_POWER_BITS = 65536;

/** Parse the registry's decimal integer form. "-0", "+1", "01" and " 1" are refused. */
export function parseBigInteger(text: string): bigint {
  if (typeof text !== "string" || !INTEGER.test(text) || text === "-0") {
    throw new RangeError(`not a whole number in decimal: "${text}"`);
  }
  return BigInt(text);
}

function bitLength(value: bigint): number {
  return value === 0n ? 0 : (value < 0n ? -value : value).toString(2).length;
}

/**
 * Raise to a power, refusing results beyond MAX_POWER_BITS before building
 * them, so a typo cannot hang the process on a million-digit number.
 */
export function powerBigInteger(base: bigint, exponent: bigint): bigint {
  if (exponent < 0n) {
    throw new RangeError("exponent must not be negative");
  }
  const magnitude = base < 0n ? -base : base;
  if (magnitude >= 2n && BigInt(bitLength(magnitude)) * exponent > BigInt(MAX_POWER_BITS)) {
    throw new RangeError(`result too large: power is limited to ${MAX_POWER_BITS} bits`);
  }
  if (magnitude <= 1n) {
    // 0^0 is 1, as in every language's integer power; (-1)^n alternates.
    if (exponent === 0n) return 1n;
    if (base === -1n) return exponent % 2n === 0n ? 1n : -1n;
    return base;
  }
  return base ** exponent;
}

/**
 * Exact integer arithmetic on decimal strings. Division truncates towards
 * zero and the remainder takes the sign of the dividend, as in TypeScript,
 * Rust, C and Java (Python floors, and is adjusted to agree).
 */
export function calculateBigInteger(a: string, op: BigIntegerOp, b: string): string {
  const x = parseBigInteger(a);
  const y = parseBigInteger(b);
  switch (op) {
    case "add":
      return (x + y).toString();
    case "subtract":
      return (x - y).toString();
    case "multiply":
      return (x * y).toString();
    case "divide":
      if (y === 0n) throw new RangeError("division by zero");
      return (x / y).toString();
    case "remainder":
      if (y === 0n) throw new RangeError("division by zero");
      return (x % y).toString();
    case "power":
      return powerBigInteger(x, y).toString();
    default:
      throw new RangeError(`unknown operation "${op}"`);
  }
}

Install

fune build

With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and nothing else, pins them in fune.lock, downloads only the TypeScript 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
Download for TypeScript math.big-integer-1.0.0-typescript.fune · 11,156 bytes sha256 95f8fcdaf187158d09f0d54be32ce1c44e288e89f508aca141fdc3331912942f

The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./math.big-integer-1.0.0-typescript.fune, or fetch it from a terminal with fune pull math.big-integer@1.0.0:typescript.

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.

CaseArgumentsExpected
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
CaseArgumentsExpected
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

PathBytes
README.md2,100
impl/python.py2,180
impl/rust.rs13,187
impl/typescript.ts2,388
vectors.json4,026