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 +167 -0
- package/LICENSE +21 -0
- package/README.md +141 -0
- package/dist/core.d.ts +26 -0
- package/dist/core.js +28 -0
- package/dist/generalized.d.ts +28 -0
- package/dist/generalized.js +81 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5 -0
- package/dist/isqrt.d.ts +22 -0
- package/dist/isqrt.js +75 -0
- package/dist/pentagonal.d.ts +21 -0
- package/dist/pentagonal.js +32 -0
- package/dist/polygonal.d.ts +54 -0
- package/dist/polygonal.js +116 -0
- package/dist/triangular.d.ts +17 -0
- package/dist/triangular.js +28 -0
- package/package.json +56 -0
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
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -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";
|
package/dist/isqrt.d.ts
ADDED
|
@@ -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
|
+
}
|