@bcts/rand 1.0.0-alpha.10

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.
@@ -0,0 +1,75 @@
1
+ // Ported from bc-rand-rust/src/magnitude.rs
2
+
3
+ /**
4
+ * Converts a signed integer to its unsigned magnitude.
5
+ * For positive numbers, returns the number unchanged.
6
+ * For negative numbers, returns the absolute value (wrapping for MIN values).
7
+ *
8
+ * This matches Rust's wrapping_abs() behavior.
9
+ */
10
+ export function toMagnitude(value: number, bits: 8 | 16 | 32): number {
11
+ switch (bits) {
12
+ case 8: {
13
+ // i8 to u8: wrapping_abs
14
+ const i8Value = (value << 24) >> 24; // Sign extend to i8
15
+ return Math.abs(i8Value) & 0xff;
16
+ }
17
+ case 16: {
18
+ // i16 to u16: wrapping_abs
19
+ const i16Value = (value << 16) >> 16; // Sign extend to i16
20
+ return Math.abs(i16Value) & 0xffff;
21
+ }
22
+ case 32: {
23
+ // i32 to u32: wrapping_abs
24
+ const i32Value = value | 0; // Force to i32
25
+ // Handle MIN_VALUE specially (wrapping behavior)
26
+ if (i32Value === -2147483648) {
27
+ return 2147483648; // Returns as positive
28
+ }
29
+ return Math.abs(i32Value) >>> 0; // Convert to unsigned
30
+ }
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Converts a signed bigint to its unsigned magnitude for 64-bit values.
36
+ */
37
+ export function toMagnitude64(value: bigint): bigint {
38
+ const mask = 0xffffffffffffffffn;
39
+ // Handle negative values
40
+ if (value < 0n) {
41
+ // Wrapping absolute value
42
+ const absValue = -value;
43
+ return absValue & mask;
44
+ }
45
+ return value & mask;
46
+ }
47
+
48
+ /**
49
+ * Converts an unsigned magnitude back to a signed value.
50
+ * Simply reinterprets the bits.
51
+ */
52
+ export function fromMagnitude(magnitude: number, bits: 8 | 16 | 32): number {
53
+ switch (bits) {
54
+ case 8:
55
+ return (magnitude << 24) >> 24; // Sign extend from u8 to i8
56
+ case 16:
57
+ return (magnitude << 16) >> 16; // Sign extend from u16 to i16
58
+ case 32:
59
+ return magnitude | 0; // Reinterpret as i32
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Converts an unsigned 64-bit magnitude back to a signed bigint.
65
+ */
66
+ export function fromMagnitude64(magnitude: bigint): bigint {
67
+ const mask = 0xffffffffffffffffn;
68
+ const signBit = 1n << 63n;
69
+ const maskedMag = magnitude & mask;
70
+ if ((maskedMag & signBit) !== 0n) {
71
+ // Negative value - convert from two's complement
72
+ return maskedMag - (1n << 64n);
73
+ }
74
+ return maskedMag;
75
+ }
@@ -0,0 +1,279 @@
1
+ // Ported from bc-rand-rust/src/random_number_generator.rs
2
+
3
+ import { wideMulU32, wideMulU64 } from "./widening.js";
4
+ import {
5
+ toMagnitude,
6
+ toMagnitude64 as _toMagnitude64,
7
+ fromMagnitude as _fromMagnitude,
8
+ fromMagnitude64 as _fromMagnitude64,
9
+ } from "./magnitude.js";
10
+
11
+ /**
12
+ * Interface for random number generators.
13
+ * This is the TypeScript equivalent of Rust's RandomNumberGenerator trait
14
+ * which extends RngCore + CryptoRng.
15
+ *
16
+ * This is compatible with the RandomNumberGenerator Swift protocol used
17
+ * in MacOS and iOS, which is important for cross-platform testing.
18
+ */
19
+ export interface RandomNumberGenerator {
20
+ /**
21
+ * Returns the next random 32-bit unsigned integer.
22
+ */
23
+ nextU32(): number;
24
+
25
+ /**
26
+ * Returns the next random 64-bit unsigned integer as a bigint.
27
+ */
28
+ nextU64(): bigint;
29
+
30
+ /**
31
+ * Fills the given Uint8Array with random bytes.
32
+ */
33
+ fillBytes(dest: Uint8Array): void;
34
+
35
+ /**
36
+ * Returns a Uint8Array of random bytes of the given size.
37
+ */
38
+ randomData(size: number): Uint8Array;
39
+
40
+ /**
41
+ * Fills the given Uint8Array with random bytes.
42
+ * Alias for fillBytes for compatibility.
43
+ */
44
+ fillRandomData(data: Uint8Array): void;
45
+ }
46
+
47
+ /**
48
+ * Returns a Uint8Array of random bytes of the given size.
49
+ */
50
+ export function rngRandomData(rng: RandomNumberGenerator, size: number): Uint8Array {
51
+ const data = new Uint8Array(size);
52
+ rng.fillRandomData(data);
53
+ return data;
54
+ }
55
+
56
+ /**
57
+ * Fills the given Uint8Array with random bytes.
58
+ */
59
+ export function rngFillRandomData(rng: RandomNumberGenerator, data: Uint8Array): void {
60
+ rng.fillRandomData(data);
61
+ }
62
+
63
+ /**
64
+ * Returns a random value that is less than the given upper bound.
65
+ *
66
+ * Uses Lemire's "nearly divisionless" method for generating random
67
+ * integers in an interval. For a detailed explanation, see:
68
+ * https://arxiv.org/abs/1805.10941
69
+ *
70
+ * @param rng - The random number generator to use
71
+ * @param upperBound - The upper bound for the randomly generated value. Must be non-zero.
72
+ * @returns A random value in the range [0, upperBound). Every value in the range is equally likely.
73
+ */
74
+ export function rngNextWithUpperBound(rng: RandomNumberGenerator, upperBound: bigint): bigint {
75
+ if (upperBound === 0n) {
76
+ throw new Error("upperBound must be non-zero");
77
+ }
78
+
79
+ // We use Lemire's "nearly divisionless" method for generating random
80
+ // integers in an interval. For a detailed explanation, see:
81
+ // https://arxiv.org/abs/1805.10941
82
+
83
+ const bitmask = 0xffffffffffffffffn; // u64 max
84
+ let random = rng.nextU64() & bitmask;
85
+ let m = wideMulU64(random, upperBound);
86
+
87
+ if (m[0] < upperBound) {
88
+ // t = (0 - upperBound) % upperBound
89
+ const negUpperBound = (bitmask + 1n - upperBound) & bitmask;
90
+ const t = negUpperBound % upperBound;
91
+ while (m[0] < t) {
92
+ random = rng.nextU64() & bitmask;
93
+ m = wideMulU64(random, upperBound);
94
+ }
95
+ }
96
+
97
+ return m[1];
98
+ }
99
+
100
+ /**
101
+ * Returns a random 32-bit value that is less than the given upper bound.
102
+ * This matches Rust's behavior when called with u32 type.
103
+ *
104
+ * Uses Lemire's "nearly divisionless" method with 32-bit arithmetic.
105
+ *
106
+ * @param rng - The random number generator to use
107
+ * @param upperBound - The upper bound for the randomly generated value. Must be non-zero and fit in u32.
108
+ * @returns A random u32 value in the range [0, upperBound).
109
+ */
110
+ export function rngNextWithUpperBoundU32(rng: RandomNumberGenerator, upperBound: number): number {
111
+ if (upperBound === 0) {
112
+ throw new Error("upperBound must be non-zero");
113
+ }
114
+
115
+ const upperBoundU32 = upperBound >>> 0;
116
+ const bitmask = 0xffffffff;
117
+
118
+ // Get random and mask to 32 bits (matches Rust behavior)
119
+ let random = Number(rng.nextU64() & BigInt(bitmask));
120
+ let m = wideMulU32(random, upperBoundU32);
121
+
122
+ if (Number(m[0]) < upperBoundU32) {
123
+ // t = (0 - upperBound) % upperBound (wrapping subtraction)
124
+ const t = ((bitmask + 1 - upperBoundU32) >>> 0) % upperBoundU32;
125
+ while (Number(m[0]) < t) {
126
+ random = Number(rng.nextU64() & BigInt(bitmask));
127
+ m = wideMulU32(random, upperBoundU32);
128
+ }
129
+ }
130
+
131
+ return Number(m[1]);
132
+ }
133
+
134
+ /**
135
+ * Returns a random value within the specified range [start, end) using 32-bit arithmetic.
136
+ * This matches Rust's behavior when called with i32 types.
137
+ *
138
+ * @param rng - The random number generator to use
139
+ * @param start - The lower bound (inclusive) as i32
140
+ * @param end - The upper bound (exclusive) as i32
141
+ * @returns A random i32 value within the bounds
142
+ */
143
+ export function rngNextInRangeI32(rng: RandomNumberGenerator, start: number, end: number): number {
144
+ if (start >= end) {
145
+ throw new Error("start must be less than end");
146
+ }
147
+
148
+ const startI32 = start | 0;
149
+ const endI32 = end | 0;
150
+ const delta = toMagnitude(endI32 - startI32, 32);
151
+
152
+ if (delta === 0xffffffff) {
153
+ return rng.nextU32() | 0;
154
+ }
155
+
156
+ const random = rngNextWithUpperBoundU32(rng, delta);
157
+ return (startI32 + random) | 0;
158
+ }
159
+
160
+ /**
161
+ * Returns a random value within the specified range [start, end).
162
+ *
163
+ * @param rng - The random number generator to use
164
+ * @param start - The lower bound (inclusive)
165
+ * @param end - The upper bound (exclusive)
166
+ * @returns A random value within the bounds of the range
167
+ */
168
+ export function rngNextInRange(rng: RandomNumberGenerator, start: bigint, end: bigint): bigint {
169
+ if (start >= end) {
170
+ throw new Error("start must be less than end");
171
+ }
172
+
173
+ const delta = end - start;
174
+
175
+ // If delta covers the entire range, just return a random value
176
+ const maxU64 = 0xffffffffffffffffn;
177
+ if (delta === maxU64) {
178
+ return rng.nextU64();
179
+ }
180
+
181
+ const random = rngNextWithUpperBound(rng, delta);
182
+ return start + random;
183
+ }
184
+
185
+ /**
186
+ * Returns a random value within the specified closed range [start, end].
187
+ *
188
+ * @param rng - The random number generator to use
189
+ * @param start - The lower bound (inclusive)
190
+ * @param end - The upper bound (inclusive)
191
+ * @returns A random value within the bounds of the range
192
+ */
193
+ export function rngNextInClosedRange(
194
+ rng: RandomNumberGenerator,
195
+ start: bigint,
196
+ end: bigint,
197
+ ): bigint {
198
+ if (start > end) {
199
+ throw new Error("start must be less than or equal to end");
200
+ }
201
+
202
+ const delta = end - start;
203
+
204
+ // If delta covers the entire range, just return a random value
205
+ const maxU64 = 0xffffffffffffffffn;
206
+ if (delta === maxU64) {
207
+ return rng.nextU64();
208
+ }
209
+
210
+ const random = rngNextWithUpperBound(rng, delta + 1n);
211
+ return start + random;
212
+ }
213
+
214
+ /**
215
+ * Returns a random value within the specified closed range [start, end] for i32 values.
216
+ * Convenience function that handles signed 32-bit integers.
217
+ *
218
+ * @param rng - The random number generator to use
219
+ * @param start - The lower bound (inclusive) as i32
220
+ * @param end - The upper bound (inclusive) as i32
221
+ * @returns A random i32 value within the bounds of the range
222
+ */
223
+ export function rngNextInClosedRangeI32(
224
+ rng: RandomNumberGenerator,
225
+ start: number,
226
+ end: number,
227
+ ): number {
228
+ if (start > end) {
229
+ throw new Error("start must be less than or equal to end");
230
+ }
231
+
232
+ // Convert to i32
233
+ const startI32 = start | 0;
234
+ const endI32 = end | 0;
235
+
236
+ // Calculate delta as u32 magnitude
237
+ const delta = toMagnitude(endI32 - startI32, 32);
238
+
239
+ // If delta covers the entire u32 range, just return a random value
240
+ if (delta === 0xffffffff) {
241
+ return rng.nextU32() | 0;
242
+ }
243
+
244
+ const random = rngNextWithUpperBoundU32(rng, delta + 1);
245
+ return (startI32 + random) | 0;
246
+ }
247
+
248
+ /**
249
+ * Returns a fixed-size array of random bytes.
250
+ *
251
+ * @param rng - The random number generator to use
252
+ * @param size - The size of the array to return
253
+ * @returns A Uint8Array of the specified size filled with random bytes
254
+ */
255
+ export function rngRandomArray(rng: RandomNumberGenerator, size: number): Uint8Array {
256
+ const data = new Uint8Array(size);
257
+ rng.fillRandomData(data);
258
+ return data;
259
+ }
260
+
261
+ /**
262
+ * Returns a random boolean value.
263
+ *
264
+ * @param rng - The random number generator to use
265
+ * @returns A random boolean
266
+ */
267
+ export function rngRandomBool(rng: RandomNumberGenerator): boolean {
268
+ return (rng.nextU32() & 1) === 0;
269
+ }
270
+
271
+ /**
272
+ * Returns a random 32-bit unsigned integer.
273
+ *
274
+ * @param rng - The random number generator to use
275
+ * @returns A random u32 value
276
+ */
277
+ export function rngRandomU32(rng: RandomNumberGenerator): number {
278
+ return rng.nextU32();
279
+ }
@@ -0,0 +1,97 @@
1
+ // Ported from bc-rand-rust/src/secure_random.rs
2
+
3
+ import type { RandomNumberGenerator } from "./random-number-generator.js";
4
+
5
+ /**
6
+ * Detects the crypto API available in the current environment.
7
+ * Works in both Node.js and browser environments.
8
+ */
9
+ function getCrypto(): Crypto {
10
+ // Browser environment
11
+ if (
12
+ typeof globalThis !== "undefined" &&
13
+ globalThis.crypto !== null &&
14
+ globalThis.crypto !== undefined
15
+ ) {
16
+ return globalThis.crypto;
17
+ }
18
+ // Node.js environment - globalThis.crypto is also available in Node.js >= 15
19
+ if (typeof globalThis.crypto !== "undefined") {
20
+ return globalThis.crypto;
21
+ }
22
+ throw new Error("No crypto API available in this environment");
23
+ }
24
+
25
+ /**
26
+ * Generate a Uint8Array of cryptographically strong random bytes of the given size.
27
+ */
28
+ export function randomData(size: number): Uint8Array {
29
+ const data = new Uint8Array(size);
30
+ fillRandomData(data);
31
+ return data;
32
+ }
33
+
34
+ /**
35
+ * Fill the given Uint8Array with cryptographically strong random bytes.
36
+ */
37
+ export function fillRandomData(data: Uint8Array): void {
38
+ const crypto = getCrypto();
39
+ crypto.getRandomValues(data);
40
+ }
41
+
42
+ /**
43
+ * Returns the next cryptographically strong random 64-bit unsigned integer.
44
+ */
45
+ export function nextU64(): bigint {
46
+ const data = new Uint8Array(8);
47
+ fillRandomData(data);
48
+ const view = new DataView(data.buffer);
49
+ return view.getBigUint64(0, true); // little-endian
50
+ }
51
+
52
+ /**
53
+ * A random number generator that can be used as a source of
54
+ * cryptographically-strong randomness.
55
+ *
56
+ * Uses the Web Crypto API (crypto.getRandomValues) which is available
57
+ * in both browsers and Node.js >= 15.
58
+ */
59
+ export class SecureRandomNumberGenerator implements RandomNumberGenerator {
60
+ /**
61
+ * Returns the next random 32-bit unsigned integer.
62
+ */
63
+ nextU32(): number {
64
+ const data = new Uint8Array(4);
65
+ fillRandomData(data);
66
+ const view = new DataView(data.buffer);
67
+ return view.getUint32(0, true) >>> 0; // little-endian, unsigned
68
+ }
69
+
70
+ /**
71
+ * Returns the next random 64-bit unsigned integer as a bigint.
72
+ */
73
+ nextU64(): bigint {
74
+ return nextU64();
75
+ }
76
+
77
+ /**
78
+ * Fills the given Uint8Array with random bytes.
79
+ */
80
+ fillBytes(dest: Uint8Array): void {
81
+ fillRandomData(dest);
82
+ }
83
+
84
+ /**
85
+ * Returns a Uint8Array of random bytes of the given size.
86
+ */
87
+ randomData(size: number): Uint8Array {
88
+ return randomData(size);
89
+ }
90
+
91
+ /**
92
+ * Fills the given Uint8Array with random bytes.
93
+ */
94
+ fillRandomData(data: Uint8Array): void {
95
+ fillRandomData(data);
96
+ }
97
+ }
@@ -0,0 +1,171 @@
1
+ // Ported from bc-rand-rust/src/seeded_random.rs
2
+
3
+ import type { RandomNumberGenerator } from "./random-number-generator.js";
4
+
5
+ /**
6
+ * Xoshiro256** state
7
+ * Based on xoshiro256** 1.0 by David Blackman and Sebastiano Vigna
8
+ * https://prng.di.unimi.it/
9
+ */
10
+ interface Xoshiro256State {
11
+ s0: bigint;
12
+ s1: bigint;
13
+ s2: bigint;
14
+ s3: bigint;
15
+ }
16
+
17
+ /**
18
+ * Rotate left for 64-bit bigint
19
+ */
20
+ function rotl(x: bigint, k: number): bigint {
21
+ const mask = 0xffffffffffffffffn;
22
+ return ((x << BigInt(k)) | (x >> BigInt(64 - k))) & mask;
23
+ }
24
+
25
+ /**
26
+ * Xoshiro256** PRNG implementation
27
+ * This is the same algorithm used by rand_xoshiro in Rust
28
+ */
29
+ function xoshiro256StarStar(state: Xoshiro256State): bigint {
30
+ const mask = 0xffffffffffffffffn;
31
+
32
+ // result = rotl(s1 * 5, 7) * 9
33
+ const result = (rotl((state.s1 * 5n) & mask, 7) * 9n) & mask;
34
+
35
+ // t = s1 << 17
36
+ const t = (state.s1 << 17n) & mask;
37
+
38
+ // Update state
39
+ state.s2 ^= state.s0;
40
+ state.s3 ^= state.s1;
41
+ state.s1 ^= state.s2;
42
+ state.s0 ^= state.s3;
43
+
44
+ state.s2 ^= t;
45
+ state.s3 = rotl(state.s3, 45);
46
+
47
+ return result;
48
+ }
49
+
50
+ /**
51
+ * A random number generator that can be used as a source of deterministic
52
+ * pseudo-randomness for testing purposes.
53
+ *
54
+ * Uses the Xoshiro256** algorithm, which is the same algorithm used by
55
+ * rand_xoshiro in Rust. This ensures cross-platform compatibility with
56
+ * the Rust implementation.
57
+ *
58
+ * WARNING: This is NOT cryptographically secure and should only be used
59
+ * for testing purposes.
60
+ */
61
+ export class SeededRandomNumberGenerator implements RandomNumberGenerator {
62
+ private readonly state: Xoshiro256State;
63
+
64
+ /**
65
+ * Creates a new seeded random number generator.
66
+ *
67
+ * The seed should be a 256-bit value, represented as an array of 4 64-bit
68
+ * integers (as bigints). For the output distribution to look random, the seed
69
+ * should not have any obvious patterns, like all zeroes or all ones.
70
+ *
71
+ * This is not cryptographically secure, and should only be used for
72
+ * testing purposes.
73
+ *
74
+ * @param seed - Array of 4 64-bit unsigned integers as bigints
75
+ */
76
+ constructor(seed: [bigint, bigint, bigint, bigint]) {
77
+ this.state = {
78
+ s0: seed[0] & 0xffffffffffffffffn,
79
+ s1: seed[1] & 0xffffffffffffffffn,
80
+ s2: seed[2] & 0xffffffffffffffffn,
81
+ s3: seed[3] & 0xffffffffffffffffn,
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Creates a new seeded random number generator from a seed array.
87
+ * Convenience method that accepts numbers and converts to bigints.
88
+ *
89
+ * @param seed - Array of 4 64-bit unsigned integers
90
+ */
91
+ static fromSeed(seed: [bigint, bigint, bigint, bigint]): SeededRandomNumberGenerator {
92
+ return new SeededRandomNumberGenerator(seed);
93
+ }
94
+
95
+ /**
96
+ * Returns the next random 64-bit unsigned integer as a bigint.
97
+ */
98
+ nextU64(): bigint {
99
+ return xoshiro256StarStar(this.state);
100
+ }
101
+
102
+ /**
103
+ * Returns the next random 32-bit unsigned integer.
104
+ */
105
+ nextU32(): number {
106
+ return Number(this.nextU64() & 0xffffffffn) >>> 0;
107
+ }
108
+
109
+ /**
110
+ * Fills the given Uint8Array with random bytes.
111
+ *
112
+ * Note: This implementation matches the Rust behavior exactly -
113
+ * it uses one nextU64() call per byte (taking only the low byte),
114
+ * which matches the Swift version's behavior.
115
+ */
116
+ fillBytes(dest: Uint8Array): void {
117
+ for (let i = 0; i < dest.length; i++) {
118
+ dest[i] = Number(this.nextU64() & 0xffn);
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Returns a Uint8Array of random bytes of the given size.
124
+ *
125
+ * This might not be the most efficient implementation,
126
+ * but it works the same as the Swift version.
127
+ */
128
+ randomData(size: number): Uint8Array {
129
+ const data = new Uint8Array(size);
130
+ for (let i = 0; i < size; i++) {
131
+ data[i] = Number(this.nextU64() & 0xffn);
132
+ }
133
+ return data;
134
+ }
135
+
136
+ /**
137
+ * Fills the given Uint8Array with random bytes.
138
+ */
139
+ fillRandomData(data: Uint8Array): void {
140
+ this.fillBytes(data);
141
+ }
142
+ }
143
+
144
+ /**
145
+ * The standard test seed used across all Blockchain Commons implementations.
146
+ */
147
+ export const TEST_SEED: [bigint, bigint, bigint, bigint] = [
148
+ 17295166580085024720n,
149
+ 422929670265678780n,
150
+ 5577237070365765850n,
151
+ 7953171132032326923n,
152
+ ];
153
+
154
+ /**
155
+ * Creates a seeded random number generator with a fixed seed.
156
+ * This is useful for reproducible testing across different platforms.
157
+ */
158
+ export function makeFakeRandomNumberGenerator(): SeededRandomNumberGenerator {
159
+ return new SeededRandomNumberGenerator(TEST_SEED);
160
+ }
161
+
162
+ /**
163
+ * Creates a Uint8Array of random data with a fixed seed.
164
+ * This is useful for reproducible testing.
165
+ *
166
+ * @param size - The number of bytes to generate
167
+ * @returns A Uint8Array of pseudo-random bytes
168
+ */
169
+ export function fakeRandomData(size: number): Uint8Array {
170
+ return makeFakeRandomNumberGenerator().randomData(size);
171
+ }
@@ -0,0 +1,70 @@
1
+ // The below is so we don't have to use #![feature(bigint_helper_methods)]
2
+ // Ported from bc-rand-rust/src/widening.rs
3
+
4
+ /**
5
+ * Wide multiplication result type - returns (low, high) parts.
6
+ * For a multiplication of two N-bit values, the result is 2N bits
7
+ * split into two N-bit parts.
8
+ */
9
+ export type WideMulResult = [bigint, bigint];
10
+
11
+ /**
12
+ * Performs wide multiplication for unsigned integers.
13
+ * Returns (low, high) parts of the full-width result.
14
+ *
15
+ * This is equivalent to Rust's widening_mul for unsigned types.
16
+ */
17
+ export function wideMul(a: bigint, b: bigint, bits: number): WideMulResult {
18
+ const mask = (1n << BigInt(bits)) - 1n;
19
+ const wide = (a & mask) * (b & mask);
20
+ const low = wide & mask;
21
+ const high = wide >> BigInt(bits);
22
+ return [low, high];
23
+ }
24
+
25
+ /**
26
+ * Wide multiplication for 8-bit unsigned integers.
27
+ * @param a - First 8-bit value
28
+ * @param b - Second 8-bit value
29
+ * @returns Tuple of (low 8 bits, high 8 bits)
30
+ */
31
+ export function wideMulU8(a: number, b: number): [number, number] {
32
+ const wide = (a & 0xff) * (b & 0xff);
33
+ return [wide & 0xff, (wide >> 8) & 0xff];
34
+ }
35
+
36
+ /**
37
+ * Wide multiplication for 16-bit unsigned integers.
38
+ * @param a - First 16-bit value
39
+ * @param b - Second 16-bit value
40
+ * @returns Tuple of (low 16 bits, high 16 bits)
41
+ */
42
+ export function wideMulU16(a: number, b: number): [number, number] {
43
+ const wide = (a & 0xffff) * (b & 0xffff);
44
+ return [wide & 0xffff, (wide >>> 16) & 0xffff];
45
+ }
46
+
47
+ /**
48
+ * Wide multiplication for 32-bit unsigned integers.
49
+ * @param a - First 32-bit value
50
+ * @param b - Second 32-bit value
51
+ * @returns Tuple of (low 32 bits, high 32 bits) as bigints
52
+ */
53
+ export function wideMulU32(a: number, b: number): [bigint, bigint] {
54
+ const aBig = BigInt(a >>> 0);
55
+ const bBig = BigInt(b >>> 0);
56
+ const wide = aBig * bBig;
57
+ return [wide & 0xffffffffn, wide >> 32n];
58
+ }
59
+
60
+ /**
61
+ * Wide multiplication for 64-bit unsigned integers.
62
+ * @param a - First 64-bit value as bigint
63
+ * @param b - Second 64-bit value as bigint
64
+ * @returns Tuple of (low 64 bits, high 64 bits) as bigints
65
+ */
66
+ export function wideMulU64(a: bigint, b: bigint): [bigint, bigint] {
67
+ const mask64 = 0xffffffffffffffffn;
68
+ const wide = (a & mask64) * (b & mask64);
69
+ return [wide & mask64, wide >> 64n];
70
+ }