figurate
v1.0.0
Published
Exact BigInt arithmetic for figurate (polygonal) numbers: forward evaluation, membership, index recovery, and iterators for triangular, pentagonal, generalized pentagonal, and every s-gonal family.
Maintainers
Readme
figurate
Exact arithmetic for figurate (polygonal) numbers over BigInt: evaluate, test membership, recover indices, and iterate — for triangular, pentagonal, generalized pentagonal, and every s-gonal family, at any magnitude.
npm install figurateimport {
triangular, isTriangular, triangularIndex,
pentagonal, pentagonalIndex,
polygonal, isPolygonal, polygonalIndex,
generalizedPentagonalNumbers,
} from "figurate";
triangular(100); // 5050n
isTriangular(5050n); // true
triangularIndex(5050n); // 100n
pentagonal(10n ** 30n); // 1499999999999999999999999999999500000000000000000000000000000n
pentagonalIndex(9223372036854775807n); // null — 2^63 - 1 is not pentagonal
polygonal(7, 10); // 235n — the 10th heptagonal number
polygonalIndex(7, 235n); // 10n
[...generalizedPentagonalNumbers({ count: 8 })];
// [0n, 1n, 2n, 5n, 7n, 12n, 15n, 22n]Zero runtime dependencies. ESM, TypeScript declarations, Node >= 18.
Why this package
Most figurate-number packages generate sequences with float arithmetic and
stop there. figurate treats the s-gonal numbers as one algebraic object:
- One general construction. Everything is
P(s, n) = ((s-2)n² - (s-4)n)/2. Triangular iss = 3, square iss = 4, pentagonal iss = 5; the named APIs are specializations, not separate implementations. - Exact at any magnitude. All arithmetic is BigInt.
numberinputs are accepted as a convenience but rejected with aRangeErrorwhen they are fractional or outside the safe-integer range — never silently rounded. - Inverses, not just generation. Membership and index recovery solve the
quadratic exactly: the discriminant
(s-4)² + 8(s-2)xmust be a perfect square (checked with an exact Newton integer square root) and the root must land on an integer index. Quadratic-residue tables refute most non-members without computing a square root at all. - Stated boundary semantics.
P(s, 0) = 0andP(s, 1) = 1are members of every family; negative values are members of none; indices are always>= 0except the signed generalized-pentagonal index. Nothing here pretends the figurate numbers are closed under ordinary arithmetic — sums and products of members are generally not members, so the API exposes evaluation, inversion, and iteration rather than fake "operations".
API
All functions accept bigint or safe-integer number and return bigint.
Invalid domains throw RangeError; wrong types throw TypeError.
General s-gonal (s ≥ 3)
| Function | Meaning |
| --- | --- |
| polygonal(s, n) | n-th s-gonal number, n >= 0 |
| isPolygonal(s, x) | is x an s-gonal number? |
| polygonalIndex(s, x) | the n with polygonal(s, n) === x, else null |
| polygonalFloorIndex(s, x) | largest n with polygonal(s, n) <= x (x >= 0) — also the count of positive members <= x |
| polygonalNumbers(s, { start?, count?, upTo? }) | generator; additive recurrence, one addition per step |
Named families
triangular, isTriangular, triangularIndex, triangularFloorIndex,
triangularNumbers — and the same five for pentagonal. These delegate to
the general construction with s fixed.
Generalized pentagonal (Euler)
g(k) = k(3k-1)/2 over all integers k, the exponents of Euler's pentagonal
number theorem (OEIS A001318):
| Function | Meaning |
| --- | --- |
| generalizedPentagonal(k) | k may be negative, zero, or positive |
| isGeneralizedPentagonal(x) | membership |
| generalizedPentagonalIndex(x) | the unique signed k, else null |
| generalizedPentagonalNumbers({ start?, count?, upTo? }) | ascending order 0, 1, 2, 5, 7, 12, 15, … (k = 0, 1, -1, 2, -2, …); start is the position in this order |
k > 0 recovers exactly the ordinary pentagonal numbers; the map is
injective over all of ℤ, so the signed index is unique.
Integer square root utilities
| Function | Meaning |
| --- | --- |
| isqrt(x) | floor(sqrt(x)), exact for any x >= 0 |
| sqrtExact(x) | the exact root if x is a perfect square, else null |
| isPerfectSquare(x) | boolean form of the above |
Generator options
start (first index / position, default 0), count (maximum yields),
upTo (inclusive value bound). With neither count nor upTo the
generators are infinite — bound them before spreading.
Semantics worth knowing
- Index origin is 0:
polygonal(s, 0) === 0n. Prefer{ start: 1 }if you want the classical1, 3, 6, 10, …without the leading zero. polygonalIndexreturnsnullfor non-members (including all negatives); it throws only for domain errors (s < 3, bad input types).- A perfect-square discriminant is necessary but not sufficient: for
s = 5, x = 2the discriminant is49 = 7²yet2is not pentagonal (it is generalized pentagonal,k = -1). The divisibility check catches this; tests pin it.
Performance
Measured with npm run bench (Node 26, Apple Silicon; medians of 5 rounds):
- Membership on random 128-bit non-members: ~126 ns/op — the residue tables (mod 64 and mod 45045) refute ~99% of non-squares before any square root, about 4.9× faster than an unconditional-isqrt implementation of the same discriminant test.
- Index recovery on ~200-bit members: ~0.8 µs/op, exact round trip.
isqrt: ~190 ns at 64 bits, ~2.3 µs at 1024 bits, ~0.5 ms at 32768 bits.- Sequence generation streams by additive recurrence (one BigInt addition per step); at small magnitudes this is a modest ~1.2× over per-index closed-form evaluation, and the gap grows with operand size.
Numbers vary by machine; the benchmark script ships in bench/ and compares
only against direct formulas running in the same harness.
Design
Formulas, domain proofs, invariants, complexity, and rejected alternatives are in DESIGN.md. The release checklist lives in docs/canonicality.md.
License
MIT © Xyra Sinclair
