Documentation / Math

Math

Precision-focused numeric utilities — C-like exactness with a Python-like API. Import from the root or from koyojs/Math.

// Root import
import { gcd, lcm, factorial, isPrime, isqrt, floorDiv, mod, comb, perm, isClose, kahanSum, roundTo } from "koyojs";

// Per-module import
import { factorial } from "koyojs/Math";

Bigint vs. number

Every function in this module is either bigint-only-output (factorial, comb, perm — by necessity, since the exact result can exceed Number.MAX_SAFE_INTEGER) or number-only. No function silently mixes the two return types. Each bigint-returning function has a documented number-only Unsafe counterpart for callers who want raw numbers and accept the precision ceiling, instead of silently overflowing.

gcdgcd(a: number, b: number): number

Greatest common divisor of two integers, computed via the Euclidean algorithm. Sign is ignored — the result is always non-negative. Throws if either argument is not an integer.

ParameterTypeDescription
anumberFirst integer.
bnumberSecond integer.
returnsnumber
import { gcd } from "koyojs";

gcd(48, 18);
// → 6

gcd(-12, 8);
// → 4

gcd(7, 0);
// → 7

lcmlcm(a: number, b: number): number

Least common multiple of two integers, derived from `gcd`. Returns 0 if either argument is 0. Throws if either argument is not an integer.

ParameterTypeDescription
anumberFirst integer.
bnumberSecond integer.
returnsnumber
import { lcm } from "koyojs";

lcm(4, 6);
// → 12

lcm(21, 6);
// → 42

lcm(0, 5);
// → 0

factorialfactorial(n: number): bigint

Exact factorial of a non-negative integer, returned as a `bigint` so results beyond `Number.MAX_SAFE_INTEGER` stay precise. Throws a RangeError if `n` is negative or not an integer. For a number-only result that throws instead of silently losing precision, use `factorialUnsafe`.

ParameterTypeDescription
nnumberNon-negative integer.
returnsbigint
import { factorial } from "koyojs";

factorial(5);
// → 120n

factorial(20);
// → 2432902008176640000n

// Stays exact well past Number.MAX_SAFE_INTEGER
factorial(25);
// → 15511210043330985984000000n

factorialUnsafefactorialUnsafe(n: number): number

Number-only factorial. Behaves like `factorial` but returns a plain `number` and throws a RangeError the moment the result would exceed `Number.MAX_SAFE_INTEGER`, instead of silently returning an imprecise value. Use `factorial` when you need the exact result beyond that ceiling.

ParameterTypeDescription
nnumberNon-negative integer.
returnsnumber
import { factorialUnsafe } from "koyojs";

factorialUnsafe(10);
// → 3628800

factorialUnsafe(21);
// → RangeError: factorialUnsafe(21) exceeds Number.MAX_SAFE_INTEGER;
//   use factorial() for an exact BigInt result

isPrimeisPrime(n: number): boolean

Primality test using 6k±1 trial division after eliminating multiples of 2 and 3. Returns `false` for numbers less than 2. Throws a TypeError if `n` is not an integer.

ParameterTypeDescription
nnumberInteger to test.
returnsboolean
import { isPrime } from "koyojs";

isPrime(2);
// → true

isPrime(17);
// → true

isPrime(1);
// → false

isPrime(100);
// → false

isqrtisqrt(n: number): number

Exact integer square root. Uses BigInt Newton's method internally so large inputs stay precise, then floors the result for non-perfect squares. Throws a RangeError if `n` is negative or not an integer.

ParameterTypeDescription
nnumberNon-negative integer.
returnsnumber
import { isqrt } from "koyojs";

isqrt(16);
// → 4

isqrt(17);
// → 4  (floored — 17 is not a perfect square)

isqrt(0);
// → 0

floorDivfloorDiv(a: number, b: number): number

Python-style floor division — always rounds toward negative infinity, unlike JavaScript's `Math.trunc(a / b)` which rounds toward zero. Throws a RangeError on division by zero.

ParameterTypeDescription
anumberDividend.
bnumberDivisor.
returnsnumber
import { floorDiv } from "koyojs";

floorDiv(7, 2);
// → 3

floorDiv(-7, 2);
// → -4   (JS `-7 / 2 | 0` gives -3 — truncation, not flooring)

floorDiv(7, -2);
// → -4

modmod(a: number, b: number): number

Python-style modulo. The result always takes the sign of the divisor, unlike JavaScript's `%` operator which takes the sign of the dividend. Built on `floorDiv`. Throws a RangeError on division by zero.

ParameterTypeDescription
anumberDividend.
bnumberDivisor.
returnsnumber
import { mod } from "koyojs";

mod(-7, 3);
// → 2    (JS `-7 % 3` gives -1)

mod(7, -3);
// → -2   (JS `7 % -3` gives 1)

mod(7, 3);
// → 1

combcomb(n: number, k: number): bigint

n choose k — the number of ways to choose k items from n without regard to order. Exact, returned as `bigint`. Returns `0n` when `k > n`. Throws a RangeError for negative or non-integer arguments. For a number-only result, use `combUnsafe`.

ParameterTypeDescription
nnumberTotal number of items.
knumberNumber of items to choose.
returnsbigint
import { comb } from "koyojs";

comb(5, 2);
// → 10n

comb(52, 5);
// → 2598960n   (5-card poker hands)

comb(4, 10);
// → 0n

combUnsafecombUnsafe(n: number, k: number): number

Number-only variant of `comb`. Throws a RangeError the moment the intermediate result would exceed `Number.MAX_SAFE_INTEGER`, instead of returning an imprecise value. Use `comb` when you need the exact result beyond that ceiling.

ParameterTypeDescription
nnumberTotal number of items.
knumberNumber of items to choose.
returnsnumber
import { combUnsafe } from "koyojs";

combUnsafe(52, 5);
// → 2598960

combUnsafe(1000, 500);
// → RangeError: combUnsafe(1000, 500) exceeds Number.MAX_SAFE_INTEGER;
//   use comb() for an exact BigInt result

permperm(n: number, k?: number): bigint

n permute k — the number of ways to arrange k items out of n, order mattering. Exact, returned as `bigint`. `k` defaults to `n` (full permutation). Returns `0n` when `k > n`. Throws a RangeError for negative or non-integer arguments. For a number-only result, use `permUnsafe`.

ParameterTypeDescription
nnumberTotal number of items.
knumberNumber of items to arrange. Defaults to `n`.
returnsbigint
import { perm } from "koyojs";

perm(5, 2);
// → 20n

perm(5);
// → 120n   (same as factorial(5))

perm(4, 10);
// → 0n

permUnsafepermUnsafe(n: number, k?: number): number

Number-only variant of `perm`. Throws a RangeError the moment the intermediate result would exceed `Number.MAX_SAFE_INTEGER`, instead of returning an imprecise value. Use `perm` when you need the exact result beyond that ceiling.

ParameterTypeDescription
nnumberTotal number of items.
knumberNumber of items to arrange. Defaults to `n`.
returnsnumber
import { permUnsafe } from "koyojs";

permUnsafe(10, 3);
// → 720

permUnsafe(20, 15);
// → RangeError: permUnsafe(20, 15) exceeds Number.MAX_SAFE_INTEGER;
//   use perm() for an exact BigInt result

isCloseisClose(a: number, b: number, options?: { relTol?: number; absTol?: number }): boolean

Python's `math.isclose` — combined relative and absolute tolerance in one call, more robust than a single fixed epsilon across widely different magnitudes. `relTol` defaults to `1e-9`, `absTol` defaults to `0`. Non-finite values (NaN, Infinity) are only equal to themselves via strict equality, never "close".

ParameterTypeDescription
anumberFirst value.
bnumberSecond value.
options.relTolnumberRelative tolerance. Default `1e-9`.
options.absTolnumberAbsolute tolerance. Default `0`.
returnsboolean
import { isClose } from "koyojs";

isClose(1.0000000001, 1.0000000002);
// → true

isClose(1000, 1000.5, { relTol: 0.001 });
// → true

isClose(0, 1e-10, { absTol: 1e-9 });
// → true   (relative tolerance breaks down near zero — use absTol)

isCloseAbsisCloseAbs(a: number, b: number, absTol: number): boolean

Thin wrapper over `isClose` with only absolute tolerance active (`relTol` forced to 0). No options object, no ambiguity about which tolerance takes priority — for callers who want a single, unambiguous absolute bound.

ParameterTypeDescription
anumberFirst value.
bnumberSecond value.
absTolnumberAbsolute tolerance.
returnsboolean
import { isCloseAbs } from "koyojs";

isCloseAbs(10.001, 10.002, 0.01);
// → true

isCloseAbs(10.001, 10.05, 0.01);
// → false

isCloseRelisCloseRel(a: number, b: number, relTol: number): boolean

Thin wrapper over `isClose` with only relative tolerance active (`absTol` forced to 0). For callers who want a single, unambiguous percentage-style bound without reaching for an options object.

ParameterTypeDescription
anumberFirst value.
bnumberSecond value.
relTolnumberRelative tolerance.
returnsboolean
import { isCloseRel } from "koyojs";

isCloseRel(1000, 1005, 0.01);
// → true   (0.5% difference, within 1% tolerance)

isCloseRel(1000, 1200, 0.01);
// → false

kahanSumkahanSum(values: number[]): number

Compensated (Kahan) summation. Tracks the lost low-order bits from each addition and feeds them back in, yielding a much lower accumulated error than a naive `reduce` over large arrays of floating-point numbers.

ParameterTypeDescription
valuesnumber[]Numbers to sum.
returnsnumber
import { kahanSum } from "koyojs";

kahanSum([0.1, 0.2, 0.3]);
// → 0.6

// Naive reduce accumulates error over many small values;
// kahanSum stays accurate at scale
const values = Array(1_000_000).fill(0.1);
kahanSum(values);
// → 100000  (naive reduce drifts noticeably by this point)

roundToroundTo(value: number, decimals?: number): number

Epsilon-corrected rounding to a given number of decimal places. Adds `Number.EPSILON` before scaling and rounding, which fixes cases like `1.005` rounding down to `1` under naive `Math.round(value * 100) / 100`. `decimals` defaults to `0`. Throws a TypeError if `decimals` is not an integer.

ParameterTypeDescription
valuenumberValue to round.
decimalsnumberDecimal places to round to. Default `0`.
returnsnumber
import { roundTo } from "koyojs";

roundTo(1.005, 2);
// → 1.01   (naive Math.round(1.005 * 100) / 100 gives 1)

roundTo(3.14159, 2);
// → 3.14

roundTo(42.7);
// → 43

copySigncopySign(x: number, y: number): number

Returns the magnitude of `x` with the sign of `y`. Correctly treats `-0` as negative, matching IEEE 754 semantics.

ParameterTypeDescription
xnumberValue supplying the magnitude.
ynumberValue supplying the sign.
returnsnumber
import { copySign } from "koyojs";

copySign(3, -5);
// → -3

copySign(-3, 5);
// → 3

copySign(3, -0);
// → -3

degreesdegrees(radians: number): number

Converts an angle from radians to degrees.

ParameterTypeDescription
radiansnumberAngle in radians.
returnsnumber
import { degrees } from "koyojs";

degrees(Math.PI);
// → 180

degrees(Math.PI / 2);
// → 90

radiansradians(degrees: number): number

Converts an angle from degrees to radians. The inverse of `degrees`.

ParameterTypeDescription
degreesnumberAngle in degrees.
returnsnumber
import { radians } from "koyojs";

radians(180);
// → 3.141592653589793

radians(90);
// → 1.5707963267948966

lerplerp(a: number, b: number, t: number): number

Linear interpolation between `a` and `b` at parameter `t`. `t = 0` returns `a`, `t = 1` returns `b`; values outside `[0, 1]` extrapolate.

ParameterTypeDescription
anumberStart value.
bnumberEnd value.
tnumberInterpolation factor.
returnsnumber
import { lerp } from "koyojs";

lerp(0, 10, 0.5);
// → 5

lerp(0, 10, 0);
// → 0

lerp(10, 20, 1.5);
// → 25   (extrapolates past b)

hypothypot(...values: number[]): number

N-dimensional hypotenuse — a thin, explicitly variadic wrapper over the platform's `Math.hypot`, kept in the API for a consistent import surface alongside `dist` and the rest of the Math module.

ParameterTypeDescription
valuesnumber[]Any number of components.
returnsnumber
import { hypot } from "koyojs";

hypot(3, 4);
// → 5

hypot(2, 3, 6);
// → 7

distdist(p: number[], q: number[]): number

Euclidean distance between two points of equal dimension. Throws a RangeError if `p` and `q` have different lengths.

ParameterTypeDescription
pnumber[]First point.
qnumber[]Second point, same dimension as `p`.
returnsnumber
import { dist } from "koyojs";

dist([0, 0], [3, 4]);
// → 5

dist([0, 0, 0], [1, 2, 2]);
// → 3

dist([1, 2], [1, 2, 3]);
// → RangeError: dist requires points of equal dimension