figurate 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/DESIGN.md ADDED
@@ -0,0 +1,167 @@
1
+ # Design
2
+
3
+ This document states what `figurate` computes, over which domains, with
4
+ which invariants, at what cost, and which alternatives were rejected.
5
+
6
+ ## The construction
7
+
8
+ For sides `s >= 3` and index `n >= 0`, the `n`-th `s`-gonal number is
9
+
10
+ ```
11
+ P(s, n) = ((s-2) n² - (s-4) n) / 2
12
+ ```
13
+
14
+ The division is exact: `n((s-2)n - (s-4))` is even for every integer `n`
15
+ (if `n` is even the first factor is; if `n` is odd, `(s-2)n - (s-4) =
16
+ (s-2)(n-1) + 2` is even). Everything in the package is this one formula:
17
+
18
+ - `s = 3` → triangular `n(n+1)/2`
19
+ - `s = 4` → squares `n²`
20
+ - `s = 5` → pentagonal `n(3n-1)/2`
21
+ - `s = 6` → hexagonal `n(2n-1)`
22
+
23
+ Named APIs (`triangular*`, `pentagonal*`) are one-line specializations of the
24
+ general functions, not parallel implementations, so every general-path test
25
+ covers them.
26
+
27
+ ### Domains
28
+
29
+ | Quantity | Domain | Violation |
30
+ | --- | --- | --- |
31
+ | `s` (sides) | integer `>= 3` | `RangeError` |
32
+ | `n` (index) | integer `>= 0` | `RangeError` |
33
+ | `k` (generalized index) | any integer | — |
34
+ | `x` (queried value) | any integer | negative → non-member (`false` / `null`) |
35
+ | `number` inputs | safe integers only | `RangeError` (fractional, non-finite, or beyond ±(2^53 − 1)) |
36
+ | non-numeric types | — | `TypeError` |
37
+
38
+ Membership queries on negative `x` answer `false` rather than throwing: the
39
+ question is well-posed and the answer is no. Domain errors (a 2-sided
40
+ "polygon", a fractional index) throw, because the question itself is
41
+ malformed. `polygonalFloorIndex` throws on `x < 0` since no index satisfies
42
+ `P(s, n) <= x` there.
43
+
44
+ ## Inversion
45
+
46
+ `P(s, n) = x` is a quadratic in `n`. With `a = s-2`, `b = s-4`:
47
+
48
+ ```
49
+ n = (b + sqrt(D)) / 2a, D = b² + 8ax
50
+ ```
51
+
52
+ `x` is an `s`-gonal number iff `D` is a perfect square **and** `b + sqrt(D)`
53
+ is divisible by `2a`. Both conditions are necessary: for `s = 5`, `x = 2`,
54
+ `D = 49` is square but `(1+7)/6` is not an integer — `2` is not pentagonal.
55
+ The second (negative) root is relevant only at `x = 0`, which is handled as
56
+ the explicit index `0` before the discriminant path runs.
57
+
58
+ The floor index (largest `n` with `P(s, n) <= x`) uses the same expression
59
+ with `isqrt` in place of the exactness requirement. Because
60
+ `isqrt(D) ∈ (sqrt(D) − 1, sqrt(D)]`, the computed candidate is off by at most
61
+ one; a single guarded correction step on each side makes it exact.
62
+
63
+ ### Exact integer square root
64
+
65
+ `isqrt` is Newton's method on BigInt, seeded with `2^ceil(bits/2)`
66
+ (computed from the hexadecimal length — a power-of-two base, so conversion
67
+ is linear). The seed is always `>= sqrt(x)`, which makes the iteration
68
+ monotonically decreasing with a clean termination condition and no
69
+ floating-point anywhere: correct at 10 bits and at 100,000 bits alike.
70
+
71
+ ### Residue pre-filters
72
+
73
+ A perfect square's residue mod `m` must be a square mod `m`. Before any
74
+ `isqrt`, the discriminant is checked against two precomputed tables:
75
+
76
+ - mod 64 — one bitwise AND, passes 12/64 of uniform inputs;
77
+ - mod 45045 = 9·5·7·11·13 — one BigInt modulo, passes ≈ 4.5%.
78
+
79
+ Together ≈ 99% of uniform non-squares are refuted by table lookups, and the
80
+ tables (64 + 45045 bytes, built once at module load) are the only
81
+ allocations on the membership hot path; measured ≈ 4.9× over unconditional
82
+ isqrt on random 128-bit non-members.
83
+
84
+ ## Generalized pentagonal numbers
85
+
86
+ `g(k) = k(3k-1)/2` over all integers `k` — the exponent sequence of Euler's
87
+ pentagonal number theorem, and the one place where a two-sided index is
88
+ mathematically the right shape (OEIS A001318). The map is injective on ℤ:
89
+ `g(k) = g(j)` forces `(k-j)(3(k+j) - 1) = 0` and the second factor is never
90
+ zero, so `generalizedPentagonalIndex` returns a unique signed `k`, with
91
+ `k > 0` exactly on the ordinary pentagonal numbers.
92
+
93
+ Inversion: `24x + 1` must be a perfect square `r²`; then `r ≡ ±1 (mod 6)`
94
+ automatically (odd squares are `1 mod 24`), and the branch is read off the
95
+ residue: `r ≡ 5 (mod 6)` → `k = (r+1)/6`, `r ≡ 1 (mod 6)` → `k = (1-r)/6`.
96
+ No divisibility check is needed — both branches always land on integers.
97
+
98
+ The ascending iterator interleaves `k = 0, 1, -1, 2, -2, …` and advances
99
+ additively: `g(-k) - g(k) = k` and `g(k+1) - g(-k) = 2k + 1`.
100
+
101
+ ## Iteration
102
+
103
+ `polygonalNumbers` uses the first-difference recurrence
104
+
105
+ ```
106
+ P(s, n+1) - P(s, n) = (s-2) n + 1
107
+ ```
108
+
109
+ one BigInt addition per yielded value (plus one to advance the difference)
110
+ after a single closed-form evaluation at `start`. This matters as operands
111
+ grow: multiplication is super-linear in operand size, addition is linear.
112
+ Generators are lazy and infinite unless bounded by `count` or the inclusive
113
+ value bound `upTo`; both bounds compose.
114
+
115
+ ## Invariants (all executable in `test/`)
116
+
117
+ - Round trip: `polygonalIndex(s, polygonal(s, n)) === n` for all valid inputs,
118
+ from `n = 0` to `n = 10^60`-scale.
119
+ - Oracle agreement: membership, index, and floor index agree with a
120
+ brute-force additive enumeration for `s = 3..12`, every `x <= 20000`.
121
+ - `isqrt` contract: `r² <= x < (r+1)²`, exhaustively to 65536 and by seeded
122
+ property tests to 4096 bits.
123
+ - Near misses: `P(s, n) ± 1` is never a member (for `n >= 4`, where gaps
124
+ exceed 2), including at `10^30`–`10^60`-scale indices.
125
+ - Identities: `T(n) + T(n-1) = n²`; `3·P₅(n) = T(3n-1)`; `P₆(n) = T(2n-1)`;
126
+ `P(s, n) = (s-2)·T(n-1) + n`; `P(s+1, n) - P(s, n) = T(n-1)`; and the
127
+ partition-counting recurrence of Euler's theorem reproduces `p(n)` up to
128
+ `n = 60` using this package's generalized pentagonal numbers.
129
+ - Ordering: the generalized iterator equals the sorted closed-form values
130
+ and is strictly increasing after the initial 0.
131
+
132
+ ## Complexity
133
+
134
+ Let `B` be the bit length of the value involved and `M(B)` the cost of a
135
+ `B`-bit multiplication in the JS engine.
136
+
137
+ | Operation | Cost |
138
+ | --- | --- |
139
+ | `polygonal` / `generalizedPentagonal` | `O(M(B))` |
140
+ | `isPolygonal` / `polygonalIndex` (non-member, filtered) | `O(B)` expected — residue lookups dominate |
141
+ | `isPolygonal` / `polygonalIndex` (member or filter survivor) | `O(M(B) log B)` — Newton isqrt |
142
+ | `polygonalFloorIndex` | `O(M(B) log B)` |
143
+ | iterator step | `O(B)` — one addition |
144
+
145
+ ## Rejected alternatives
146
+
147
+ - **Float fast paths** (`Math.sqrt` seeding or full float inverses below
148
+ 2^53): rejected. Two arithmetic regimes double the test surface and invite
149
+ the exact class of silent boundary bugs this package exists to rule out.
150
+ One exact path, uniformly.
151
+ - **Silent `number` coercion**: rejected. Accepting `4.5` or `2^53` and
152
+ rounding would return confidently wrong answers; the contract is loud
153
+ rejection and BigInt as the primary domain.
154
+ - **Bespoke per-family inverses** (dedicated triangular/pentagonal
155
+ formulas): rejected as implementations, kept as documentation. The general
156
+ derivation specializes to them exactly; one code path means the oracle
157
+ tests certify every family at once.
158
+ - **Operations pretending closure** (adding/multiplying figurate numbers as
159
+ if the result were figurate): rejected as dishonest API shape. The
160
+ algebra that *is* real — identities across families — ships as executable
161
+ tests instead.
162
+ - **A `start`-less generalized iterator**: rejected; the ascending order has
163
+ a natural position semantics (`0 → k=0`, `2j-1 → k=j`, `2j → k=-j`) that
164
+ makes resumption cheap and testable.
165
+ - **Throwing on non-membership**: rejected in favor of `null` / `false`.
166
+ Non-membership is an ordinary answer, not an error; exceptions are
167
+ reserved for malformed questions.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Xyra Sinclair
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # figurate
2
+
3
+ Exact arithmetic for figurate (polygonal) numbers over BigInt: evaluate,
4
+ test membership, recover indices, and iterate — for triangular, pentagonal,
5
+ generalized pentagonal, and every s-gonal family, at any magnitude.
6
+
7
+ ```sh
8
+ npm install figurate
9
+ ```
10
+
11
+ ```js
12
+ import {
13
+ triangular, isTriangular, triangularIndex,
14
+ pentagonal, pentagonalIndex,
15
+ polygonal, isPolygonal, polygonalIndex,
16
+ generalizedPentagonalNumbers,
17
+ } from "figurate";
18
+
19
+ triangular(100); // 5050n
20
+ isTriangular(5050n); // true
21
+ triangularIndex(5050n); // 100n
22
+ pentagonal(10n ** 30n); // 1499999999999999999999999999999500000000000000000000000000000n
23
+ pentagonalIndex(9223372036854775807n); // null — 2^63 - 1 is not pentagonal
24
+ polygonal(7, 10); // 235n — the 10th heptagonal number
25
+ polygonalIndex(7, 235n); // 10n
26
+ [...generalizedPentagonalNumbers({ count: 8 })];
27
+ // [0n, 1n, 2n, 5n, 7n, 12n, 15n, 22n]
28
+ ```
29
+
30
+ Zero runtime dependencies. ESM, TypeScript declarations, Node >= 18.
31
+
32
+ ## Why this package
33
+
34
+ Most figurate-number packages generate sequences with float arithmetic and
35
+ stop there. `figurate` treats the s-gonal numbers as one algebraic object:
36
+
37
+ - **One general construction.** Everything is `P(s, n) = ((s-2)n² - (s-4)n)/2`.
38
+ Triangular is `s = 3`, square is `s = 4`, pentagonal is `s = 5`; the named
39
+ APIs are specializations, not separate implementations.
40
+ - **Exact at any magnitude.** All arithmetic is BigInt. `number` inputs are
41
+ accepted as a convenience but rejected with a `RangeError` when they are
42
+ fractional or outside the safe-integer range — never silently rounded.
43
+ - **Inverses, not just generation.** Membership and index recovery solve the
44
+ quadratic exactly: the discriminant `(s-4)² + 8(s-2)x` must be a perfect
45
+ square (checked with an exact Newton integer square root) *and* the root
46
+ must land on an integer index. Quadratic-residue tables refute most
47
+ non-members without computing a square root at all.
48
+ - **Stated boundary semantics.** `P(s, 0) = 0` and `P(s, 1) = 1` are members
49
+ of every family; negative values are members of none; indices are always
50
+ `>= 0` except the signed generalized-pentagonal index. Nothing here
51
+ pretends the figurate numbers are closed under ordinary arithmetic — sums
52
+ and products of members are generally not members, so the API exposes
53
+ evaluation, inversion, and iteration rather than fake "operations".
54
+
55
+ ## API
56
+
57
+ All functions accept `bigint` or safe-integer `number` and return `bigint`.
58
+ Invalid domains throw `RangeError`; wrong types throw `TypeError`.
59
+
60
+ ### General s-gonal (s ≥ 3)
61
+
62
+ | Function | Meaning |
63
+ | --- | --- |
64
+ | `polygonal(s, n)` | `n`-th `s`-gonal number, `n >= 0` |
65
+ | `isPolygonal(s, x)` | is `x` an `s`-gonal number? |
66
+ | `polygonalIndex(s, x)` | the `n` with `polygonal(s, n) === x`, else `null` |
67
+ | `polygonalFloorIndex(s, x)` | largest `n` with `polygonal(s, n) <= x` (`x >= 0`) — also the count of positive members `<= x` |
68
+ | `polygonalNumbers(s, { start?, count?, upTo? })` | generator; additive recurrence, one addition per step |
69
+
70
+ ### Named families
71
+
72
+ `triangular`, `isTriangular`, `triangularIndex`, `triangularFloorIndex`,
73
+ `triangularNumbers` — and the same five for `pentagonal`. These delegate to
74
+ the general construction with `s` fixed.
75
+
76
+ ### Generalized pentagonal (Euler)
77
+
78
+ `g(k) = k(3k-1)/2` over all integers `k`, the exponents of Euler's pentagonal
79
+ number theorem (OEIS A001318):
80
+
81
+ | Function | Meaning |
82
+ | --- | --- |
83
+ | `generalizedPentagonal(k)` | `k` may be negative, zero, or positive |
84
+ | `isGeneralizedPentagonal(x)` | membership |
85
+ | `generalizedPentagonalIndex(x)` | the unique signed `k`, else `null` |
86
+ | `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 |
87
+
88
+ `k > 0` recovers exactly the ordinary pentagonal numbers; the map is
89
+ injective over all of ℤ, so the signed index is unique.
90
+
91
+ ### Integer square root utilities
92
+
93
+ | Function | Meaning |
94
+ | --- | --- |
95
+ | `isqrt(x)` | `floor(sqrt(x))`, exact for any `x >= 0` |
96
+ | `sqrtExact(x)` | the exact root if `x` is a perfect square, else `null` |
97
+ | `isPerfectSquare(x)` | boolean form of the above |
98
+
99
+ ### Generator options
100
+
101
+ `start` (first index / position, default 0), `count` (maximum yields),
102
+ `upTo` (inclusive value bound). With neither `count` nor `upTo` the
103
+ generators are infinite — bound them before spreading.
104
+
105
+ ## Semantics worth knowing
106
+
107
+ - Index origin is 0: `polygonal(s, 0) === 0n`. Prefer `{ start: 1 }` if you
108
+ want the classical `1, 3, 6, 10, …` without the leading zero.
109
+ - `polygonalIndex` returns `null` for non-members (including all negatives);
110
+ it throws only for domain errors (`s < 3`, bad input types).
111
+ - A perfect-square discriminant is necessary but not sufficient: for
112
+ `s = 5, x = 2` the discriminant is `49 = 7²` yet `2` is not pentagonal
113
+ (it is *generalized* pentagonal, `k = -1`). The divisibility check catches
114
+ this; tests pin it.
115
+
116
+ ## Performance
117
+
118
+ Measured with `npm run bench` (Node 26, Apple Silicon; medians of 5 rounds):
119
+
120
+ - Membership on random 128-bit non-members: ~126 ns/op — the residue tables
121
+ (mod 64 and mod 45045) refute ~99% of non-squares before any square root,
122
+ about 4.9× faster than an unconditional-isqrt implementation of the same
123
+ discriminant test.
124
+ - Index recovery on ~200-bit members: ~0.8 µs/op, exact round trip.
125
+ - `isqrt`: ~190 ns at 64 bits, ~2.3 µs at 1024 bits, ~0.5 ms at 32768 bits.
126
+ - Sequence generation streams by additive recurrence (one BigInt addition
127
+ per step); at small magnitudes this is a modest ~1.2× over per-index
128
+ closed-form evaluation, and the gap grows with operand size.
129
+
130
+ Numbers vary by machine; the benchmark script ships in `bench/` and compares
131
+ only against direct formulas running in the same harness.
132
+
133
+ ## Design
134
+
135
+ Formulas, domain proofs, invariants, complexity, and rejected alternatives
136
+ are in [DESIGN.md](./DESIGN.md). The release checklist lives in
137
+ [docs/canonicality.md](./docs/canonicality.md).
138
+
139
+ ## License
140
+
141
+ MIT © Xyra Sinclair
package/dist/core.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Integer input accepted anywhere `figurate` takes an integer: a `bigint`, or
3
+ * a `number` that is a safe integer (`Number.isSafeInteger`). Fractional,
4
+ * non-finite, and unsafe-magnitude numbers are rejected with a `RangeError`
5
+ * rather than silently rounded; non-numeric types are rejected with a
6
+ * `TypeError`. All results are returned as `bigint`.
7
+ */
8
+ export type IntLike = bigint | number;
9
+ /** Options shared by the sequence generators. */
10
+ export interface SequenceOptions {
11
+ /**
12
+ * Index (for `polygonalNumbers` and friends) or ascending position (for
13
+ * `generalizedPentagonalNumbers`) of the first value yielded. Default `0`.
14
+ */
15
+ start?: IntLike;
16
+ /** Maximum count of values to yield. Omit for no count limit. */
17
+ count?: IntLike;
18
+ /**
19
+ * Inclusive upper bound on yielded values; the generator returns as soon as
20
+ * the next value would exceed it. Omit for no value limit.
21
+ */
22
+ upTo?: IntLike;
23
+ }
24
+ export declare function toBigInt(value: IntLike, name: string): bigint;
25
+ export declare function toSides(s: IntLike): bigint;
26
+ export declare function toIndex(n: IntLike, name?: string): bigint;
package/dist/core.js ADDED
@@ -0,0 +1,28 @@
1
+ export function toBigInt(value, name) {
2
+ if (typeof value === "bigint")
3
+ return value;
4
+ if (typeof value === "number") {
5
+ if (!Number.isInteger(value)) {
6
+ throw new RangeError(`${name} must be an integer; got ${value}`);
7
+ }
8
+ if (!Number.isSafeInteger(value)) {
9
+ throw new RangeError(`${name} = ${value} is outside Number's safe-integer range; pass a bigint instead`);
10
+ }
11
+ return BigInt(value);
12
+ }
13
+ throw new TypeError(`${name} must be a bigint or a safe-integer number; got ${typeof value}`);
14
+ }
15
+ export function toSides(s) {
16
+ const v = toBigInt(s, "sides");
17
+ if (v < 3n) {
18
+ throw new RangeError(`sides must be >= 3; got ${v}`);
19
+ }
20
+ return v;
21
+ }
22
+ export function toIndex(n, name = "n") {
23
+ const v = toBigInt(n, name);
24
+ if (v < 0n) {
25
+ throw new RangeError(`${name} must be >= 0; got ${v}`);
26
+ }
27
+ return v;
28
+ }
@@ -0,0 +1,28 @@
1
+ import { type IntLike, type SequenceOptions } from "./core.js";
2
+ /**
3
+ * The generalized pentagonal number `g(k) = k(3k-1)/2` for any integer `k`,
4
+ * positive, zero, or negative. These are the exponents in Euler's pentagonal
5
+ * number theorem; in ascending order (`k = 0, 1, -1, 2, -2, ...`) they run
6
+ * `0, 1, 2, 5, 7, 12, 15, 22, 26, ...` (OEIS A001318).
7
+ *
8
+ * The map is injective over all of `Z`: `g(k) = g(j)` forces
9
+ * `(k - j)(3(k + j) - 1) = 0`, and `3(k + j) = 1` has no integer solution.
10
+ */
11
+ export declare function generalizedPentagonal(k: IntLike): bigint;
12
+ /**
13
+ * The unique integer `k` (possibly negative) with
14
+ * `generalizedPentagonal(k) === x`, or `null` if `x` is not a generalized
15
+ * pentagonal number. `k > 0` means `x` is also an ordinary pentagonal number.
16
+ */
17
+ export declare function generalizedPentagonalIndex(x: IntLike): bigint | null;
18
+ /** Whether `x` is a generalized pentagonal number. */
19
+ export declare function isGeneralizedPentagonal(x: IntLike): boolean;
20
+ /**
21
+ * Yields generalized pentagonal numbers in ascending order — the canonical
22
+ * `k = 0, 1, -1, 2, -2, ...` interleaving. `start` is the position in this
23
+ * ascending order (position `0` is `g(0) = 0`, position `2j-1` is `g(j)`,
24
+ * position `2j` is `g(-j)`); `count` and `upTo` behave as in
25
+ * `polygonalNumbers`. Steps are additive: `g(-j) - g(j) = j` and
26
+ * `g(j+1) - g(-j) = 2j + 1`.
27
+ */
28
+ export declare function generalizedPentagonalNumbers(options?: SequenceOptions): Generator<bigint, void, void>;
@@ -0,0 +1,81 @@
1
+ import { toBigInt } from "./core.js";
2
+ import { sqrtExactBig } from "./isqrt.js";
3
+ import { normalizeSequenceOptions } from "./polygonal.js";
4
+ /**
5
+ * The generalized pentagonal number `g(k) = k(3k-1)/2` for any integer `k`,
6
+ * positive, zero, or negative. These are the exponents in Euler's pentagonal
7
+ * number theorem; in ascending order (`k = 0, 1, -1, 2, -2, ...`) they run
8
+ * `0, 1, 2, 5, 7, 12, 15, 22, 26, ...` (OEIS A001318).
9
+ *
10
+ * The map is injective over all of `Z`: `g(k) = g(j)` forces
11
+ * `(k - j)(3(k + j) - 1) = 0`, and `3(k + j) = 1` has no integer solution.
12
+ */
13
+ export function generalizedPentagonal(k) {
14
+ const K = toBigInt(k, "k");
15
+ return (K * (3n * K - 1n)) / 2n;
16
+ }
17
+ /** Internal: signed-index inverse on an already-validated bigint. */
18
+ function generalizedPentagonalIndexOf(x) {
19
+ if (x < 0n)
20
+ return null;
21
+ // x = k(3k-1)/2 iff 24x + 1 = (6k - 1)^2. If r = sqrt(24x + 1) exists it
22
+ // is odd with r^2 = 1 (mod 24), so r = 1 or 5 (mod 6): r = 5 (mod 6) gives
23
+ // the positive branch k = (r+1)/6, r = 1 (mod 6) the branch k = (1-r)/6.
24
+ const r = sqrtExactBig(24n * x + 1n);
25
+ if (r === null)
26
+ return null;
27
+ return r % 6n === 5n ? (r + 1n) / 6n : (1n - r) / 6n;
28
+ }
29
+ /**
30
+ * The unique integer `k` (possibly negative) with
31
+ * `generalizedPentagonal(k) === x`, or `null` if `x` is not a generalized
32
+ * pentagonal number. `k > 0` means `x` is also an ordinary pentagonal number.
33
+ */
34
+ export function generalizedPentagonalIndex(x) {
35
+ return generalizedPentagonalIndexOf(toBigInt(x, "x"));
36
+ }
37
+ /** Whether `x` is a generalized pentagonal number. */
38
+ export function isGeneralizedPentagonal(x) {
39
+ return generalizedPentagonalIndexOf(toBigInt(x, "x")) !== null;
40
+ }
41
+ /**
42
+ * Yields generalized pentagonal numbers in ascending order — the canonical
43
+ * `k = 0, 1, -1, 2, -2, ...` interleaving. `start` is the position in this
44
+ * ascending order (position `0` is `g(0) = 0`, position `2j-1` is `g(j)`,
45
+ * position `2j` is `g(-j)`); `count` and `upTo` behave as in
46
+ * `polygonalNumbers`. Steps are additive: `g(-j) - g(j) = j` and
47
+ * `g(j+1) - g(-j) = 2j + 1`.
48
+ */
49
+ export function* generalizedPentagonalNumbers(options = {}) {
50
+ const { start, count, upTo } = normalizeSequenceOptions(options);
51
+ // Decode the start position into (j, onPositiveBranch) and one closed-form
52
+ // evaluation; every later value is one or two bigint additions away.
53
+ let j = start === 0n ? 0n : (start + 1n) >> 1n;
54
+ let onPos = start !== 0n && (start & 1n) === 1n;
55
+ let value = j === 0n
56
+ ? 0n
57
+ : onPos
58
+ ? (j * (3n * j - 1n)) / 2n
59
+ : (j * (3n * j + 1n)) / 2n;
60
+ let emitted = 0n;
61
+ while (count === null || emitted < count) {
62
+ if (upTo !== null && value > upTo)
63
+ return;
64
+ yield value;
65
+ emitted += 1n;
66
+ if (j === 0n) {
67
+ j = 1n;
68
+ onPos = true;
69
+ value = 1n;
70
+ }
71
+ else if (onPos) {
72
+ value += j; // g(j) -> g(-j)
73
+ onPos = false;
74
+ }
75
+ else {
76
+ value += 2n * j + 1n; // g(-j) -> g(j+1)
77
+ j += 1n;
78
+ onPos = true;
79
+ }
80
+ }
81
+ }
@@ -0,0 +1,6 @@
1
+ export type { IntLike, SequenceOptions } from "./core.js";
2
+ export { isPerfectSquare, isqrt, sqrtExact } from "./isqrt.js";
3
+ export { isPolygonal, polygonal, polygonalFloorIndex, polygonalIndex, polygonalNumbers, } from "./polygonal.js";
4
+ export { isTriangular, triangular, triangularFloorIndex, triangularIndex, triangularNumbers, } from "./triangular.js";
5
+ export { isPentagonal, pentagonal, pentagonalFloorIndex, pentagonalIndex, pentagonalNumbers, } from "./pentagonal.js";
6
+ export { generalizedPentagonal, generalizedPentagonalIndex, generalizedPentagonalNumbers, isGeneralizedPentagonal, } from "./generalized.js";
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { isPerfectSquare, isqrt, sqrtExact } from "./isqrt.js";
2
+ export { isPolygonal, polygonal, polygonalFloorIndex, polygonalIndex, polygonalNumbers, } from "./polygonal.js";
3
+ export { isTriangular, triangular, triangularFloorIndex, triangularIndex, triangularNumbers, } from "./triangular.js";
4
+ export { isPentagonal, pentagonal, pentagonalFloorIndex, pentagonalIndex, pentagonalNumbers, } from "./pentagonal.js";
5
+ export { generalizedPentagonal, generalizedPentagonalIndex, generalizedPentagonalNumbers, isGeneralizedPentagonal, } from "./generalized.js";
@@ -0,0 +1,22 @@
1
+ import { type IntLike } from "./core.js";
2
+ /**
3
+ * Floor of the exact integer square root of a non-negative integer.
4
+ *
5
+ * Newton's method on bigints, seeded with `2^ceil(bits/2)` which is always
6
+ * `>= sqrt(x)`, so the iteration decreases monotonically and stops at
7
+ * `floor(sqrt(x))` after O(log bits) steps. Exact at any magnitude; never
8
+ * routes through floating point.
9
+ *
10
+ * @throws RangeError if `x < 0` or `x` is not a valid integer input.
11
+ */
12
+ export declare function isqrt(x: IntLike): bigint;
13
+ /** Internal: `sqrtExact` on an already-validated bigint. */
14
+ export declare function sqrtExactBig(n: bigint): bigint | null;
15
+ /**
16
+ * The exact integer square root of `x` if `x` is a perfect square, otherwise
17
+ * `null`. Negative inputs return `null` (they are never perfect squares).
18
+ * Non-squares are usually refuted by residue tables without an isqrt.
19
+ */
20
+ export declare function sqrtExact(x: IntLike): bigint | null;
21
+ /** Whether `x` is a perfect square. Equivalent to `sqrtExact(x) !== null`. */
22
+ export declare function isPerfectSquare(x: IntLike): boolean;
package/dist/isqrt.js ADDED
@@ -0,0 +1,75 @@
1
+ import { toBigInt } from "./core.js";
2
+ /**
3
+ * Quadratic-residue tables for fast perfect-square rejection. A perfect
4
+ * square's residue mod m must itself be a square mod m, so a table lookup
5
+ * refutes most non-squares without computing an integer square root.
6
+ *
7
+ * mod 64 is a single bitwise AND and rejects 52/64 of uniform non-squares;
8
+ * mod 45045 = 9*5*7*11*13 costs one bigint modulo and cuts the survivors to
9
+ * about 4.5%. Together roughly 99% of uniform non-squares never reach isqrt.
10
+ */
11
+ function residueTable(m) {
12
+ const t = new Uint8Array(m);
13
+ for (let i = 0; i < m; i++)
14
+ t[(i * i) % m] = 1;
15
+ return t;
16
+ }
17
+ const SQ_MOD_64 = residueTable(64);
18
+ const SQ_MOD_45045 = residueTable(45045);
19
+ /**
20
+ * Bit length of a positive bigint. Hexadecimal conversion is linear in the
21
+ * operand size (base 16 is a power of two), unlike decimal conversion.
22
+ */
23
+ function bitLength(x) {
24
+ const hex = x.toString(16);
25
+ const top = parseInt(hex[0], 16);
26
+ return BigInt((hex.length - 1) * 4 + (32 - Math.clz32(top)));
27
+ }
28
+ /**
29
+ * Floor of the exact integer square root of a non-negative integer.
30
+ *
31
+ * Newton's method on bigints, seeded with `2^ceil(bits/2)` which is always
32
+ * `>= sqrt(x)`, so the iteration decreases monotonically and stops at
33
+ * `floor(sqrt(x))` after O(log bits) steps. Exact at any magnitude; never
34
+ * routes through floating point.
35
+ *
36
+ * @throws RangeError if `x < 0` or `x` is not a valid integer input.
37
+ */
38
+ export function isqrt(x) {
39
+ const n = toBigInt(x, "x");
40
+ if (n < 0n) {
41
+ throw new RangeError(`isqrt is undefined for negative inputs; got ${n}`);
42
+ }
43
+ if (n < 2n)
44
+ return n;
45
+ let r = 1n << ((bitLength(n) + 1n) >> 1n);
46
+ let next = (r + n / r) >> 1n;
47
+ while (next < r) {
48
+ r = next;
49
+ next = (r + n / r) >> 1n;
50
+ }
51
+ return r;
52
+ }
53
+ /** Internal: `sqrtExact` on an already-validated bigint. */
54
+ export function sqrtExactBig(n) {
55
+ if (n < 0n)
56
+ return null;
57
+ if (SQ_MOD_64[Number(n & 63n)] === 0)
58
+ return null;
59
+ if (SQ_MOD_45045[Number(n % 45045n)] === 0)
60
+ return null;
61
+ const r = isqrt(n);
62
+ return r * r === n ? r : null;
63
+ }
64
+ /**
65
+ * The exact integer square root of `x` if `x` is a perfect square, otherwise
66
+ * `null`. Negative inputs return `null` (they are never perfect squares).
67
+ * Non-squares are usually refuted by residue tables without an isqrt.
68
+ */
69
+ export function sqrtExact(x) {
70
+ return sqrtExactBig(toBigInt(x, "x"));
71
+ }
72
+ /** Whether `x` is a perfect square. Equivalent to `sqrtExact(x) !== null`. */
73
+ export function isPerfectSquare(x) {
74
+ return sqrtExactBig(toBigInt(x, "x")) !== null;
75
+ }
@@ -0,0 +1,21 @@
1
+ import { type IntLike, type SequenceOptions } from "./core.js";
2
+ /**
3
+ * The `n`-th pentagonal number, `P(n) = n(3n-1)/2 = polygonal(5, n)`.
4
+ * The discriminant specializes to `24x + 1`.
5
+ */
6
+ export declare function pentagonal(n: IntLike): bigint;
7
+ /**
8
+ * Whether `x` is a pentagonal number, i.e. `24x + 1` is a perfect square
9
+ * whose root is `5 (mod 6)` (or `x` is 0). Only non-negative indices count;
10
+ * for the two-sided family see `isGeneralizedPentagonal`.
11
+ */
12
+ export declare function isPentagonal(x: IntLike): boolean;
13
+ /**
14
+ * The index `n >= 0` with `pentagonal(n) === x`, or `null` if `x` is not
15
+ * pentagonal.
16
+ */
17
+ export declare function pentagonalIndex(x: IntLike): bigint | null;
18
+ /** The largest `n >= 0` with `pentagonal(n) <= x`. Requires `x >= 0`. */
19
+ export declare function pentagonalFloorIndex(x: IntLike): bigint;
20
+ /** Yields pentagonal numbers; see `polygonalNumbers` for option semantics. */
21
+ export declare function pentagonalNumbers(options?: SequenceOptions): Generator<bigint, void, void>;
@@ -0,0 +1,32 @@
1
+ import {} from "./core.js";
2
+ import { isPolygonal, polygonal, polygonalFloorIndex, polygonalIndex, polygonalNumbers, } from "./polygonal.js";
3
+ /**
4
+ * The `n`-th pentagonal number, `P(n) = n(3n-1)/2 = polygonal(5, n)`.
5
+ * The discriminant specializes to `24x + 1`.
6
+ */
7
+ export function pentagonal(n) {
8
+ return polygonal(5n, n);
9
+ }
10
+ /**
11
+ * Whether `x` is a pentagonal number, i.e. `24x + 1` is a perfect square
12
+ * whose root is `5 (mod 6)` (or `x` is 0). Only non-negative indices count;
13
+ * for the two-sided family see `isGeneralizedPentagonal`.
14
+ */
15
+ export function isPentagonal(x) {
16
+ return isPolygonal(5n, x);
17
+ }
18
+ /**
19
+ * The index `n >= 0` with `pentagonal(n) === x`, or `null` if `x` is not
20
+ * pentagonal.
21
+ */
22
+ export function pentagonalIndex(x) {
23
+ return polygonalIndex(5n, x);
24
+ }
25
+ /** The largest `n >= 0` with `pentagonal(n) <= x`. Requires `x >= 0`. */
26
+ export function pentagonalFloorIndex(x) {
27
+ return polygonalFloorIndex(5n, x);
28
+ }
29
+ /** Yields pentagonal numbers; see `polygonalNumbers` for option semantics. */
30
+ export function pentagonalNumbers(options = {}) {
31
+ return polygonalNumbers(5n, options);
32
+ }
@@ -0,0 +1,54 @@
1
+ import { type IntLike, type SequenceOptions } from "./core.js";
2
+ /** Internal: P(s, n) on already-validated bigints. */
3
+ export declare function polygonalOf(s: bigint, n: bigint): bigint;
4
+ /**
5
+ * The `n`-th `s`-gonal number, `P(s, n) = ((s-2)n^2 - (s-4)n) / 2`.
6
+ *
7
+ * Domains: `s >= 3`, `n >= 0`. `P(s, 0) = 0` and `P(s, 1) = 1` for every
8
+ * family; `P(s, 2) = s`.
9
+ *
10
+ * @throws RangeError if `s < 3`, `n < 0`, or an input is not a valid integer.
11
+ */
12
+ export declare function polygonal(s: IntLike, n: IntLike): bigint;
13
+ /** Internal: inverse on already-validated bigints. */
14
+ export declare function polygonalIndexOf(s: bigint, x: bigint): bigint | null;
15
+ /**
16
+ * The index `n >= 0` with `polygonal(s, n) === x`, or `null` if `x` is not an
17
+ * `s`-gonal number. Negative `x` returns `null`. Exact at any magnitude: the
18
+ * discriminant `(s-4)^2 + 8(s-2)x` must be a perfect square and the root must
19
+ * land on an integer index.
20
+ *
21
+ * @throws RangeError if `s < 3` or an input is not a valid integer.
22
+ */
23
+ export declare function polygonalIndex(s: IntLike, x: IntLike): bigint | null;
24
+ /**
25
+ * Whether `x` is an `s`-gonal number. `0` and `1` are members of every
26
+ * family (indices 0 and 1); negative `x` is never a member.
27
+ *
28
+ * @throws RangeError if `s < 3` or an input is not a valid integer.
29
+ */
30
+ export declare function isPolygonal(s: IntLike, x: IntLike): boolean;
31
+ /**
32
+ * The largest index `n >= 0` with `polygonal(s, n) <= x`, i.e. the count of
33
+ * positive `s`-gonal numbers `<= x`. Requires `x >= 0` (every `n` satisfies
34
+ * `polygonal(s, n) > x` when `x < 0`, so no floor index exists).
35
+ *
36
+ * @throws RangeError if `s < 3`, `x < 0`, or an input is not a valid integer.
37
+ */
38
+ export declare function polygonalFloorIndex(s: IntLike, x: IntLike): bigint;
39
+ export declare function normalizeSequenceOptions(options: SequenceOptions): {
40
+ start: bigint;
41
+ count: bigint | null;
42
+ upTo: bigint | null;
43
+ };
44
+ /**
45
+ * Yields `s`-gonal numbers `polygonal(s, n)` for `n = start, start+1, ...`,
46
+ * bounded by `count` and/or the inclusive value bound `upTo`. With neither
47
+ * bound the generator is infinite. Values are produced by the additive
48
+ * recurrence `P(s, n+1) - P(s, n) = (s-2)n + 1` — one bigint addition per
49
+ * step after a single closed-form evaluation at `start`.
50
+ *
51
+ * @throws RangeError if `s < 3`, `start < 0`, `count < 0`, or an input is not
52
+ * a valid integer.
53
+ */
54
+ export declare function polygonalNumbers(s: IntLike, options?: SequenceOptions): Generator<bigint, void, void>;
@@ -0,0 +1,116 @@
1
+ import { toBigInt, toIndex, toSides, } from "./core.js";
2
+ import { isqrt, sqrtExactBig } from "./isqrt.js";
3
+ /** Internal: P(s, n) on already-validated bigints. */
4
+ export function polygonalOf(s, n) {
5
+ // n((s-2)n - (s-4)) is always even: n even makes it even, and n odd makes
6
+ // (s-2)n - (s-4) = (s-2)(n-1) + 2 even, so the division by 2 is exact.
7
+ return ((s - 2n) * n * n - (s - 4n) * n) / 2n;
8
+ }
9
+ /**
10
+ * The `n`-th `s`-gonal number, `P(s, n) = ((s-2)n^2 - (s-4)n) / 2`.
11
+ *
12
+ * Domains: `s >= 3`, `n >= 0`. `P(s, 0) = 0` and `P(s, 1) = 1` for every
13
+ * family; `P(s, 2) = s`.
14
+ *
15
+ * @throws RangeError if `s < 3`, `n < 0`, or an input is not a valid integer.
16
+ */
17
+ export function polygonal(s, n) {
18
+ return polygonalOf(toSides(s), toIndex(n));
19
+ }
20
+ /** Internal: inverse on already-validated bigints. */
21
+ export function polygonalIndexOf(s, x) {
22
+ if (x < 0n)
23
+ return null;
24
+ if (x === 0n)
25
+ return 0n; // the quadratic's other root; not on the + branch
26
+ // Solve (s-2)n^2 - (s-4)n - 2x = 0 for the positive root:
27
+ // n = ((s-4) + sqrt(D)) / (2(s-2)) with D = (s-4)^2 + 8(s-2)x.
28
+ const a = s - 2n;
29
+ const b = s - 4n;
30
+ const r = sqrtExactBig(b * b + 8n * a * x);
31
+ if (r === null)
32
+ return null;
33
+ const numerator = b + r;
34
+ const denominator = 2n * a;
35
+ if (numerator % denominator !== 0n)
36
+ return null;
37
+ return numerator / denominator;
38
+ }
39
+ /**
40
+ * The index `n >= 0` with `polygonal(s, n) === x`, or `null` if `x` is not an
41
+ * `s`-gonal number. Negative `x` returns `null`. Exact at any magnitude: the
42
+ * discriminant `(s-4)^2 + 8(s-2)x` must be a perfect square and the root must
43
+ * land on an integer index.
44
+ *
45
+ * @throws RangeError if `s < 3` or an input is not a valid integer.
46
+ */
47
+ export function polygonalIndex(s, x) {
48
+ return polygonalIndexOf(toSides(s), toBigInt(x, "x"));
49
+ }
50
+ /**
51
+ * Whether `x` is an `s`-gonal number. `0` and `1` are members of every
52
+ * family (indices 0 and 1); negative `x` is never a member.
53
+ *
54
+ * @throws RangeError if `s < 3` or an input is not a valid integer.
55
+ */
56
+ export function isPolygonal(s, x) {
57
+ return polygonalIndexOf(toSides(s), toBigInt(x, "x")) !== null;
58
+ }
59
+ /**
60
+ * The largest index `n >= 0` with `polygonal(s, n) <= x`, i.e. the count of
61
+ * positive `s`-gonal numbers `<= x`. Requires `x >= 0` (every `n` satisfies
62
+ * `polygonal(s, n) > x` when `x < 0`, so no floor index exists).
63
+ *
64
+ * @throws RangeError if `s < 3`, `x < 0`, or an input is not a valid integer.
65
+ */
66
+ export function polygonalFloorIndex(s, x) {
67
+ const S = toSides(s);
68
+ const X = toBigInt(x, "x");
69
+ if (X < 0n) {
70
+ throw new RangeError(`x must be >= 0 for a floor index; got ${X}`);
71
+ }
72
+ if (X === 0n)
73
+ return 0n;
74
+ const a = S - 2n;
75
+ const b = S - 4n;
76
+ // isqrt floors the real sqrt, so m is floor(real root) or one below it;
77
+ // b + isqrt(D) >= 0 for all s >= 3, so bigint division here is a floor.
78
+ let m = (b + isqrt(b * b + 8n * a * X)) / (2n * a);
79
+ while (polygonalOf(S, m + 1n) <= X)
80
+ m += 1n;
81
+ while (m > 0n && polygonalOf(S, m) > X)
82
+ m -= 1n;
83
+ return m;
84
+ }
85
+ export function normalizeSequenceOptions(options) {
86
+ const start = toIndex(options.start ?? 0n, "start");
87
+ const count = options.count === undefined ? null : toIndex(options.count, "count");
88
+ const upTo = options.upTo === undefined ? null : toBigInt(options.upTo, "upTo");
89
+ return { start, count, upTo };
90
+ }
91
+ /**
92
+ * Yields `s`-gonal numbers `polygonal(s, n)` for `n = start, start+1, ...`,
93
+ * bounded by `count` and/or the inclusive value bound `upTo`. With neither
94
+ * bound the generator is infinite. Values are produced by the additive
95
+ * recurrence `P(s, n+1) - P(s, n) = (s-2)n + 1` — one bigint addition per
96
+ * step after a single closed-form evaluation at `start`.
97
+ *
98
+ * @throws RangeError if `s < 3`, `start < 0`, `count < 0`, or an input is not
99
+ * a valid integer.
100
+ */
101
+ export function* polygonalNumbers(s, options = {}) {
102
+ const S = toSides(s);
103
+ const { start, count, upTo } = normalizeSequenceOptions(options);
104
+ const step = S - 2n;
105
+ let value = polygonalOf(S, start);
106
+ let delta = step * start + 1n;
107
+ let emitted = 0n;
108
+ while (count === null || emitted < count) {
109
+ if (upTo !== null && value > upTo)
110
+ return;
111
+ yield value;
112
+ emitted += 1n;
113
+ value += delta;
114
+ delta += step;
115
+ }
116
+ }
@@ -0,0 +1,17 @@
1
+ import { type IntLike, type SequenceOptions } from "./core.js";
2
+ /**
3
+ * The `n`-th triangular number, `T(n) = n(n+1)/2 = polygonal(3, n)`.
4
+ * The discriminant specializes to `8x + 1`.
5
+ */
6
+ export declare function triangular(n: IntLike): bigint;
7
+ /** Whether `x` is a triangular number, i.e. `8x + 1` is a perfect square. */
8
+ export declare function isTriangular(x: IntLike): boolean;
9
+ /**
10
+ * The index `n >= 0` with `triangular(n) === x`, or `null` if `x` is not
11
+ * triangular.
12
+ */
13
+ export declare function triangularIndex(x: IntLike): bigint | null;
14
+ /** The largest `n >= 0` with `triangular(n) <= x`. Requires `x >= 0`. */
15
+ export declare function triangularFloorIndex(x: IntLike): bigint;
16
+ /** Yields triangular numbers; see `polygonalNumbers` for option semantics. */
17
+ export declare function triangularNumbers(options?: SequenceOptions): Generator<bigint, void, void>;
@@ -0,0 +1,28 @@
1
+ import {} from "./core.js";
2
+ import { isPolygonal, polygonal, polygonalFloorIndex, polygonalIndex, polygonalNumbers, } from "./polygonal.js";
3
+ /**
4
+ * The `n`-th triangular number, `T(n) = n(n+1)/2 = polygonal(3, n)`.
5
+ * The discriminant specializes to `8x + 1`.
6
+ */
7
+ export function triangular(n) {
8
+ return polygonal(3n, n);
9
+ }
10
+ /** Whether `x` is a triangular number, i.e. `8x + 1` is a perfect square. */
11
+ export function isTriangular(x) {
12
+ return isPolygonal(3n, x);
13
+ }
14
+ /**
15
+ * The index `n >= 0` with `triangular(n) === x`, or `null` if `x` is not
16
+ * triangular.
17
+ */
18
+ export function triangularIndex(x) {
19
+ return polygonalIndex(3n, x);
20
+ }
21
+ /** The largest `n >= 0` with `triangular(n) <= x`. Requires `x >= 0`. */
22
+ export function triangularFloorIndex(x) {
23
+ return polygonalFloorIndex(3n, x);
24
+ }
25
+ /** Yields triangular numbers; see `polygonalNumbers` for option semantics. */
26
+ export function triangularNumbers(options = {}) {
27
+ return polygonalNumbers(3n, options);
28
+ }
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "figurate",
3
+ "version": "1.0.0",
4
+ "description": "Exact BigInt arithmetic for figurate (polygonal) numbers: forward evaluation, membership, index recovery, and iterators for triangular, pentagonal, generalized pentagonal, and every s-gonal family.",
5
+ "keywords": [
6
+ "figurate",
7
+ "polygonal",
8
+ "triangular",
9
+ "pentagonal",
10
+ "generalized-pentagonal",
11
+ "bigint",
12
+ "exact",
13
+ "integer-square-root",
14
+ "number-theory",
15
+ "math"
16
+ ],
17
+ "license": "MIT",
18
+ "author": "Xyra Sinclair",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/XyraSinclair/figurate.git"
22
+ },
23
+ "homepage": "https://github.com/XyraSinclair/figurate#readme",
24
+ "bugs": {
25
+ "url": "https://github.com/XyraSinclair/figurate/issues"
26
+ },
27
+ "type": "module",
28
+ "main": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "import": "./dist/index.js"
34
+ }
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "DESIGN.md"
39
+ ],
40
+ "sideEffects": false,
41
+ "engines": {
42
+ "node": ">=18"
43
+ },
44
+ "scripts": {
45
+ "build": "tsc -p tsconfig.build.json",
46
+ "test": "npm run build && tsc -p tsconfig.test.json && node --test build/test/*.test.js",
47
+ "bench": "npm run build && node bench/run.mjs",
48
+ "smoke": "bash scripts/smoke.sh",
49
+ "release": "npm publish --access public",
50
+ "prepublishOnly": "npm test && npm run smoke"
51
+ },
52
+ "devDependencies": {
53
+ "@types/node": "^18.19.130",
54
+ "typescript": "^5.5.0"
55
+ }
56
+ }