@wtasnorg/node-lib 0.0.10 → 0.0.12

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.
Files changed (62) hide show
  1. package/changelog.txt +14 -0
  2. package/docs/README.md +17 -0
  3. package/docs/docs.json +2148 -413
  4. package/docs/functions/countOnes.md +31 -0
  5. package/docs/functions/countZeroes.md +32 -0
  6. package/docs/functions/countZeroesWithWidth.md +37 -0
  7. package/docs/functions/createFindDirectories.md +3 -3
  8. package/docs/functions/decode.md +3 -3
  9. package/docs/functions/decode32.md +49 -0
  10. package/docs/functions/decode58.md +49 -0
  11. package/docs/functions/decode85.md +49 -0
  12. package/docs/functions/encode.md +3 -3
  13. package/docs/functions/encode32.md +45 -0
  14. package/docs/functions/encode58.md +45 -0
  15. package/docs/functions/encode85.md +45 -0
  16. package/docs/functions/hello.md +1 -1
  17. package/docs/functions/parseUserAgent.md +1 -1
  18. package/docs/functions/pojo.md +1 -1
  19. package/docs/functions/popcount32.md +27 -0
  20. package/docs/functions/popcount64.md +31 -0
  21. package/docs/interfaces/FileSystemDependencies.md +3 -3
  22. package/docs/interfaces/FindDirectoriesOptions.md +5 -5
  23. package/docs/interfaces/UserAgentInfo.md +6 -6
  24. package/docs/type-aliases/Base32CharsetType.md +13 -0
  25. package/docs/type-aliases/Base58CharsetType.md +13 -0
  26. package/docs/type-aliases/Base64CharsetType.md +1 -1
  27. package/docs/type-aliases/Base85CharsetType.md +13 -0
  28. package/docs/variables/Base32Charset.md +16 -0
  29. package/docs/variables/Base58Charset.md +16 -0
  30. package/docs/variables/Base64Charset.md +1 -1
  31. package/docs/variables/Base85Charset.md +16 -0
  32. package/package.json +52 -8
  33. package/readme.txt +30 -4
  34. package/src/base32.d.ts +58 -0
  35. package/src/base32.js +143 -0
  36. package/src/base32.test.d.ts +2 -0
  37. package/src/base32.test.js +121 -0
  38. package/src/base32.test.ts +144 -0
  39. package/src/base32.ts +169 -0
  40. package/src/base58.d.ts +58 -0
  41. package/src/base58.js +155 -0
  42. package/src/base58.test.d.ts +2 -0
  43. package/src/base58.test.js +108 -0
  44. package/src/base58.test.ts +128 -0
  45. package/src/base58.ts +177 -0
  46. package/src/base85.d.ts +58 -0
  47. package/src/base85.js +173 -0
  48. package/src/base85.test.d.ts +2 -0
  49. package/src/base85.test.js +107 -0
  50. package/src/base85.test.ts +125 -0
  51. package/src/base85.ts +199 -0
  52. package/src/bits.d.ts +43 -0
  53. package/src/bits.js +117 -0
  54. package/src/bits.test.d.ts +2 -0
  55. package/src/bits.test.js +62 -0
  56. package/src/bits.test.ts +77 -0
  57. package/src/bits.ts +135 -0
  58. package/src/index.d.ts +9 -2
  59. package/src/index.js +5 -1
  60. package/src/index.ts +26 -2
  61. package/.github/workflows/npm-publish.yml +0 -36
  62. package/.github/workflows/npm-test-on-pr.yml +0 -30
@@ -0,0 +1,125 @@
1
+ import { describe, it } from "node:test";
2
+ import { strictEqual, throws } from "node:assert";
3
+ import { encode85, decode85, Base85Charset } from "./base85.js";
4
+ import type { Base85CharsetType } from "./base85.js";
5
+
6
+ describe("Base85 encode85", () => {
7
+ it("encodes empty string", () => {
8
+ strictEqual(encode85(""), "");
9
+ });
10
+
11
+ it("encodes 'Hello' with ascii85 charset", () => {
12
+ const encoded = encode85("Hello");
13
+ strictEqual(encoded.length > 0, true);
14
+ });
15
+
16
+ it("encodes single character", () => {
17
+ const encoded = encode85("A");
18
+ strictEqual(encoded.length, 2);
19
+ });
20
+
21
+ it("encodes 4 characters (full block)", () => {
22
+ const encoded = encode85("test");
23
+ strictEqual(encoded.length, 5);
24
+ });
25
+
26
+ it("encodes 5 characters (4 + 1)", () => {
27
+ const encoded = encode85("tests");
28
+ strictEqual(encoded.length, 7);
29
+ });
30
+
31
+ it("encodes with z85 charset", () => {
32
+ const ascii85 = encode85("test", "ascii85");
33
+ const z85 = encode85("test", "z85");
34
+ strictEqual(ascii85 !== z85, true);
35
+ });
36
+
37
+ it("encodes with rfc1924 charset", () => {
38
+ const ascii85 = encode85("test", "ascii85");
39
+ const rfc1924 = encode85("test", "rfc1924");
40
+ strictEqual(ascii85 !== rfc1924, true);
41
+ });
42
+
43
+ it("encodes UTF-8 characters", () => {
44
+ const input = "こんにちは";
45
+ const encoded = encode85(input);
46
+ const decoded = decode85(encoded);
47
+ strictEqual(decoded, input);
48
+ });
49
+ });
50
+
51
+ describe("Base85 decode85", () => {
52
+ it("decodes empty string", () => {
53
+ strictEqual(decode85(""), "");
54
+ });
55
+
56
+ it("decodes encoded 'Hello'", () => {
57
+ const encoded = encode85("Hello");
58
+ strictEqual(decode85(encoded), "Hello");
59
+ });
60
+
61
+ it("decodes single character encoded", () => {
62
+ const encoded = encode85("A");
63
+ strictEqual(decode85(encoded), "A");
64
+ });
65
+
66
+ it("decodes 4-char block correctly", () => {
67
+ const encoded = encode85("test");
68
+ strictEqual(decode85(encoded), "test");
69
+ });
70
+
71
+ it("throws on invalid character", () => {
72
+ // Use a character not in ascii85 alphabet
73
+ throws(() => decode85("\x7F"), /Invalid Base85 character/);
74
+ });
75
+ });
76
+
77
+ describe("Base85 round-trip", () => {
78
+ const testCases = [
79
+ "",
80
+ "a",
81
+ "ab",
82
+ "abc",
83
+ "abcd",
84
+ "abcde",
85
+ "Hello, World!",
86
+ "The quick brown fox jumps over the lazy dog",
87
+ "こんにちは世界",
88
+ "🎉🚀✨",
89
+ "1234567890"
90
+ ];
91
+
92
+ for (const charset of Base85Charset) {
93
+ describe(`charset: ${charset}`, () => {
94
+ for (const input of testCases) {
95
+ it(`round-trips: ${JSON.stringify(input)}`, () => {
96
+ const encoded = encode85(input, charset);
97
+ const decoded = decode85(encoded, charset);
98
+ strictEqual(decoded, input);
99
+ });
100
+ }
101
+ });
102
+ }
103
+ });
104
+
105
+ describe("Base85Charset", () => {
106
+ it("exports array with three charsets", () => {
107
+ strictEqual(Base85Charset.length, 3);
108
+ strictEqual(Base85Charset[0], "ascii85");
109
+ strictEqual(Base85Charset[1], "z85");
110
+ strictEqual(Base85Charset[2], "rfc1924");
111
+ });
112
+
113
+ it("charset type works correctly", () => {
114
+ const cs: Base85CharsetType = "z85";
115
+ strictEqual(encode85("test", cs), encode85("test", "z85"));
116
+ });
117
+
118
+ it("each charset has exactly 85 characters", () => {
119
+ // This is a compile-time guarantee, but let's verify at runtime too
120
+ for (const charset of Base85Charset) {
121
+ const encoded = encode85("test", charset);
122
+ strictEqual(encoded.length > 0, true);
123
+ }
124
+ });
125
+ });
package/src/base85.ts ADDED
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Base85 encoding/decoding with multiple charset variants.
3
+ * Base85 provides ~25% better efficiency than Base64 (4:5 vs 3:4 ratio).
4
+ * @module base85
5
+ */
6
+
7
+ /**
8
+ * Available Base85 charset variants.
9
+ * - `ascii85`: Adobe Ascii85 (btoa format)
10
+ * - `z85`: ZeroMQ Base85 (no quotes or backslash)
11
+ * - `rfc1924`: RFC 1924 IPv6 encoding
12
+ */
13
+ const Base85Charset = ["ascii85", "z85", "rfc1924"] as const;
14
+
15
+ /**
16
+ * Base85 charset type.
17
+ */
18
+ type Base85CharsetType = (typeof Base85Charset)[number];
19
+
20
+ /**
21
+ * Charset alphabets for Base85 variants.
22
+ * Each has exactly 85 printable ASCII characters.
23
+ */
24
+ const CHARSETS: Record<Base85CharsetType, string> = {
25
+ ascii85: "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstu",
26
+ z85: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#",
27
+ rfc1924: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"
28
+ };
29
+
30
+ /**
31
+ * Build a reverse lookup table for decoding.
32
+ * @param alphabet - The 85-character alphabet string.
33
+ * @returns Map of character to index.
34
+ */
35
+ function buildDecodeTable(alphabet: string): Map<string, number> {
36
+ const table = new Map<string, number>();
37
+ for (let i = 0; i < alphabet.length; i++) {
38
+ const char = alphabet[i];
39
+ if (char !== undefined) {
40
+ table.set(char, i);
41
+ }
42
+ }
43
+ return table;
44
+ }
45
+
46
+ /**
47
+ * Pre-built decode tables for each charset.
48
+ */
49
+ const DECODE_TABLES: Record<Base85CharsetType, Map<string, number>> = {
50
+ ascii85: buildDecodeTable(CHARSETS.ascii85),
51
+ z85: buildDecodeTable(CHARSETS.z85),
52
+ rfc1924: buildDecodeTable(CHARSETS.rfc1924)
53
+ };
54
+
55
+ /**
56
+ * Encode a string to Base85.
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * import { encode85 } from "./base85.js";
61
+ *
62
+ * encode85("Hello");
63
+ * // => "87cURDZ"
64
+ *
65
+ * encode85("test", "z85");
66
+ * // => "wrx.P"
67
+ * ```
68
+ *
69
+ * @param input - The string to encode.
70
+ * @param charset - The charset variant to use (default: "ascii85").
71
+ * @returns The Base85 encoded string.
72
+ */
73
+ function encode85(input: string, charset: Base85CharsetType = "ascii85"): string {
74
+ if (input === "") {
75
+ return "";
76
+ }
77
+
78
+ const alphabet = CHARSETS[charset];
79
+ const bytes = new TextEncoder().encode(input);
80
+ let result = "";
81
+
82
+ // Process 4 bytes at a time
83
+ for (let i = 0; i < bytes.length; i += 4) {
84
+ const chunkSize = Math.min(4, bytes.length - i);
85
+
86
+ // Pack up to 4 bytes into a value using multiplication to avoid
87
+ // 32-bit signed integer overflow from bitwise operations
88
+ let value = 0;
89
+ for (let j = 0; j < chunkSize; j++) {
90
+ value = value * 256 + (bytes[i + j] ?? 0);
91
+ }
92
+ // Pad with zeros for incomplete chunks
93
+ for (let j = chunkSize; j < 4; j++) {
94
+ value = value * 256;
95
+ }
96
+
97
+ // Convert to 5 base-85 digits
98
+ const encoded: string[] = [];
99
+ for (let j = 0; j < 5; j++) {
100
+ encoded.unshift(alphabet[value % 85] ?? "");
101
+ value = Math.floor(value / 85);
102
+ }
103
+
104
+ // For the last chunk, only output as many characters as needed
105
+ // (chunkSize bytes -> chunkSize + 1 characters)
106
+ if (i + 4 > bytes.length) {
107
+ result += encoded.slice(0, chunkSize + 1).join("");
108
+ } else {
109
+ result += encoded.join("");
110
+ }
111
+ }
112
+
113
+ return result;
114
+ }
115
+
116
+ /**
117
+ * Decode a Base85 string.
118
+ *
119
+ * @example
120
+ * ```typescript
121
+ * import { decode85 } from "./base85.js";
122
+ *
123
+ * decode85("87cURDZ");
124
+ * // => "Hello"
125
+ *
126
+ * decode85("wrx.P", "z85");
127
+ * // => "test"
128
+ * ```
129
+ *
130
+ * @param input - The Base85 encoded string.
131
+ * @param charset - The charset variant to use (default: "ascii85").
132
+ * @returns The decoded string.
133
+ * @throws Error if the input contains invalid characters.
134
+ */
135
+ function decode85(input: string, charset: Base85CharsetType = "ascii85"): string {
136
+ if (input === "") {
137
+ return "";
138
+ }
139
+
140
+ const decodeTable = DECODE_TABLES[charset];
141
+ const bytes: number[] = [];
142
+ const inputLen = input.length;
143
+
144
+ // Calculate expected output length
145
+ // Full groups of 5 chars -> 4 bytes each
146
+ // Partial group: n chars -> n-1 bytes
147
+ const fullGroups = Math.floor(inputLen / 5);
148
+ const remainder = inputLen % 5;
149
+ const totalBytes = fullGroups * 4 + (remainder > 0 ? remainder - 1 : 0);
150
+
151
+ // Process 5 characters at a time
152
+ let byteCount = 0;
153
+ for (let i = 0; i < inputLen; i += 5) {
154
+ const chunkSize = Math.min(5, inputLen - i);
155
+
156
+ // Decode up to 5 characters into a 32-bit value
157
+ let value = 0;
158
+ for (let j = 0; j < chunkSize; j++) {
159
+ const char = input[i + j];
160
+ if (char === undefined) {
161
+ break;
162
+ }
163
+ const digit = decodeTable.get(char);
164
+ if (digit === undefined) {
165
+ throw new Error(`Invalid Base85 character: ${char}`);
166
+ }
167
+ value = value * 85 + digit;
168
+ }
169
+
170
+ // Pad with 84 (highest digit) for incomplete chunks
171
+ for (let j = chunkSize; j < 5; j++) {
172
+ value = value * 85 + 84;
173
+ }
174
+
175
+ // Calculate how many bytes this chunk should produce
176
+ const bytesToExtract = chunkSize === 5 ? 4 : chunkSize - 1;
177
+
178
+ // Extract bytes from high to low
179
+ const extracted: number[] = [];
180
+ for (let j = 0; j < 4; j++) {
181
+ extracted.unshift(value & 0xff);
182
+ value = value >>> 8;
183
+ }
184
+
185
+ // Only push the bytes we need
186
+ for (let j = 0; j < bytesToExtract && byteCount < totalBytes; j++) {
187
+ const byte = extracted[j];
188
+ if (byte !== undefined) {
189
+ bytes.push(byte);
190
+ byteCount++;
191
+ }
192
+ }
193
+ }
194
+
195
+ return new TextDecoder().decode(new Uint8Array(bytes));
196
+ }
197
+
198
+ export { encode85, decode85, Base85Charset };
199
+ export type { Base85CharsetType };
package/src/bits.d.ts ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Count set bits in an unsigned 32-bit representation of a number.
3
+ *
4
+ * @param {number} value - Number interpreted as uint32.
5
+ * @returns {number} Number of set bits (0-32).
6
+ */
7
+ declare function popcount32(value: number): number;
8
+ /**
9
+ * Count set bits in the low 64 bits of a number or bigint.
10
+ *
11
+ * @param {number | bigint} value - Input value. For `number`, it must be a non-negative safe integer.
12
+ * @returns {number} Number of set bits (0-64).
13
+ * @throws {RangeError} If `value` is negative or an unsafe integer.
14
+ */
15
+ declare function popcount64(value: number | bigint): number;
16
+ /**
17
+ * Count set bits in a non-negative integer.
18
+ *
19
+ * @param {number | bigint} value - Input value.
20
+ * @returns {number} Number of set bits.
21
+ * @throws {RangeError} If `value` is negative or an unsafe integer.
22
+ */
23
+ declare function countOnes(value: number | bigint): number;
24
+ /**
25
+ * Count zero bits within a fixed width, considering only low-order `width` bits.
26
+ *
27
+ * @param {number | bigint} value - Input value.
28
+ * @param {number} width - Bit width to inspect.
29
+ * @returns {number} Number of zero bits in the selected width.
30
+ * @throws {RangeError} If `value` is negative/unsafe or `width` is negative/unsafe.
31
+ */
32
+ declare function countZeroesWithWidth(value: number | bigint, width: number): number;
33
+ /**
34
+ * Count zero bits from LSB up to and including the highest set bit.
35
+ * For zero input, returns 0.
36
+ *
37
+ * @param {number | bigint} value - Input value.
38
+ * @returns {number} Zero count up to the left-most set bit.
39
+ * @throws {RangeError} If `value` is negative or an unsafe integer.
40
+ */
41
+ declare function countZeroes(value: number | bigint): number;
42
+ export { popcount32, popcount64, countOnes, countZeroesWithWidth, countZeroes };
43
+ //# sourceMappingURL=bits.d.ts.map
package/src/bits.js ADDED
@@ -0,0 +1,117 @@
1
+ const UINT64_MASK = (1n << 64n) - 1n;
2
+ /**
3
+ *
4
+ * @param {number | bigint} value
5
+ * @param {string} fnName
6
+ * @returns {bigint}
7
+ * @throws {RangeError} If `value` is negative or an unsafe integer.
8
+ * @example
9
+ * toNonNegativeBigInt(42, "exampleFunction"); // returns 42n
10
+ * toNonNegativeBigInt(-1, "exampleFunction"); // throws RangeError
11
+ */
12
+ function toNonNegativeBigInt(value, fnName) {
13
+ if (typeof value === "bigint") {
14
+ if (value < 0n) {
15
+ throw new RangeError(`${fnName}: value must be non-negative`);
16
+ }
17
+ return value;
18
+ }
19
+ if (!Number.isSafeInteger(value) || value < 0) {
20
+ throw new RangeError(`${fnName}: value must be a non-negative safe integer`);
21
+ }
22
+ return BigInt(value);
23
+ }
24
+ /**
25
+ * Count set bits in a bigint.
26
+ *
27
+ * @param {bigint} value - Input value.
28
+ * @returns {number} Number of set bits.
29
+ * @example
30
+ * popcountBigInt(0b1011n); // returns 3
31
+ */
32
+ function popcountBigInt(value) {
33
+ let count = 0;
34
+ let bits = value;
35
+ while (bits !== 0n) {
36
+ bits &= bits - 1n;
37
+ count += 1;
38
+ }
39
+ return count;
40
+ }
41
+ /**
42
+ * Count set bits in an unsigned 32-bit representation of a number.
43
+ *
44
+ * @param {number} value - Number interpreted as uint32.
45
+ * @returns {number} Number of set bits (0-32).
46
+ */
47
+ function popcount32(value) {
48
+ let bits = value >>> 0;
49
+ bits -= (bits >>> 1) & 0x55555555;
50
+ bits = (bits & 0x33333333) + ((bits >>> 2) & 0x33333333);
51
+ bits = (bits + (bits >>> 4)) & 0x0f0f0f0f;
52
+ bits += bits >>> 8;
53
+ bits += bits >>> 16;
54
+ return bits & 0x3f;
55
+ }
56
+ /**
57
+ * Count set bits in the low 64 bits of a number or bigint.
58
+ *
59
+ * @param {number | bigint} value - Input value. For `number`, it must be a non-negative safe integer.
60
+ * @returns {number} Number of set bits (0-64).
61
+ * @throws {RangeError} If `value` is negative or an unsafe integer.
62
+ */
63
+ function popcount64(value) {
64
+ const bits64 = toNonNegativeBigInt(value, "popcount64") & UINT64_MASK;
65
+ return popcountBigInt(bits64);
66
+ }
67
+ /**
68
+ * Count set bits in a non-negative integer.
69
+ *
70
+ * @param {number | bigint} value - Input value.
71
+ * @returns {number} Number of set bits.
72
+ * @throws {RangeError} If `value` is negative or an unsafe integer.
73
+ */
74
+ function countOnes(value) {
75
+ const bits = toNonNegativeBigInt(value, "countOnes");
76
+ return popcountBigInt(bits);
77
+ }
78
+ /**
79
+ * Count zero bits within a fixed width, considering only low-order `width` bits.
80
+ *
81
+ * @param {number | bigint} value - Input value.
82
+ * @param {number} width - Bit width to inspect.
83
+ * @returns {number} Number of zero bits in the selected width.
84
+ * @throws {RangeError} If `value` is negative/unsafe or `width` is negative/unsafe.
85
+ */
86
+ function countZeroesWithWidth(value, width) {
87
+ if (!Number.isSafeInteger(width) || width < 0) {
88
+ throw new RangeError("countZeroesWithWidth: width must be a non-negative safe integer");
89
+ }
90
+ if (width === 0) {
91
+ return 0;
92
+ }
93
+ const bits = toNonNegativeBigInt(value, "countZeroesWithWidth");
94
+ const widthBigInt = BigInt(width);
95
+ const mask = (1n << widthBigInt) - 1n;
96
+ const ones = popcountBigInt(bits & mask);
97
+ return width - ones;
98
+ }
99
+ /**
100
+ * Count zero bits from LSB up to and including the highest set bit.
101
+ * For zero input, returns 0.
102
+ *
103
+ * @param {number | bigint} value - Input value.
104
+ * @returns {number} Zero count up to the left-most set bit.
105
+ * @throws {RangeError} If `value` is negative or an unsafe integer.
106
+ */
107
+ function countZeroes(value) {
108
+ const bits = toNonNegativeBigInt(value, "countZeroes");
109
+ if (bits === 0n) {
110
+ return 0;
111
+ }
112
+ const width = bits.toString(2).length;
113
+ const ones = popcountBigInt(bits);
114
+ return width - ones;
115
+ }
116
+ export { popcount32, popcount64, countOnes, countZeroesWithWidth, countZeroes };
117
+ //# sourceMappingURL=bits.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=bits.test.d.ts.map
@@ -0,0 +1,62 @@
1
+ import { describe, it } from "node:test";
2
+ import { strictEqual, throws } from "node:assert";
3
+ import { popcount32, popcount64, countOnes, countZeroesWithWidth, countZeroes } from "./bits.js";
4
+ describe("popcount32", () => {
5
+ it("counts set bits for simple values", () => {
6
+ strictEqual(popcount32(0), 0);
7
+ strictEqual(popcount32(1), 1);
8
+ strictEqual(popcount32(0b10110100), 4);
9
+ });
10
+ it("uses uint32 semantics", () => {
11
+ strictEqual(popcount32(-1), 32);
12
+ strictEqual(popcount32(0xffffffff), 32);
13
+ });
14
+ });
15
+ describe("popcount64", () => {
16
+ it("counts set bits for bigint values", () => {
17
+ strictEqual(popcount64(0n), 0);
18
+ strictEqual(popcount64((1n << 64n) - 1n), 64);
19
+ strictEqual(popcount64(41n), 3);
20
+ });
21
+ it("counts only low 64 bits", () => {
22
+ const value = (1n << 65n) | 1n;
23
+ strictEqual(popcount64(value), 1);
24
+ });
25
+ it("throws for invalid number input", () => {
26
+ throws(() => popcount64(-1), /must be a non-negative safe integer/);
27
+ throws(() => popcount64(Number.MAX_SAFE_INTEGER + 1), /must be a non-negative safe integer/);
28
+ });
29
+ });
30
+ describe("countOnes", () => {
31
+ it("counts set bits for number and bigint", () => {
32
+ strictEqual(countOnes(0), 0);
33
+ strictEqual(countOnes(255), 8);
34
+ strictEqual(countOnes(65537n), 2);
35
+ });
36
+ });
37
+ describe("countZeroesWithWidth", () => {
38
+ it("counts zero bits for a chosen width", () => {
39
+ strictEqual(countZeroesWithWidth(0b10100, 5), 3);
40
+ strictEqual(countZeroesWithWidth(0b10100, 8), 6);
41
+ strictEqual(countZeroesWithWidth(0, 8), 8);
42
+ strictEqual(countZeroesWithWidth(123, 0), 0);
43
+ });
44
+ it("throws for invalid width", () => {
45
+ throws(() => countZeroesWithWidth(1, -1), /width must be a non-negative safe integer/);
46
+ throws(() => countZeroesWithWidth(1, Number.MAX_SAFE_INTEGER + 1), /width must be a non-negative safe integer/);
47
+ });
48
+ });
49
+ describe("countZeroes", () => {
50
+ it("counts zero bits only up to highest set bit", () => {
51
+ strictEqual(countZeroes(0), 0);
52
+ strictEqual(countZeroes(1), 0);
53
+ strictEqual(countZeroes(0b10100), 3);
54
+ strictEqual(countZeroes(0b1000), 3);
55
+ strictEqual(countZeroes(0b1111), 0);
56
+ });
57
+ it("throws for invalid inputs", () => {
58
+ throws(() => countZeroes(-1), /must be a non-negative safe integer/);
59
+ throws(() => countOnes(-1n), /must be non-negative/);
60
+ });
61
+ });
62
+ //# sourceMappingURL=bits.test.js.map
@@ -0,0 +1,77 @@
1
+ import { describe, it } from "node:test";
2
+ import { strictEqual, throws } from "node:assert";
3
+ import {
4
+ popcount32,
5
+ popcount64,
6
+ countOnes,
7
+ countZeroesWithWidth,
8
+ countZeroes
9
+ } from "./bits.js";
10
+
11
+ describe("popcount32", () => {
12
+ it("counts set bits for simple values", () => {
13
+ strictEqual(popcount32(0), 0);
14
+ strictEqual(popcount32(1), 1);
15
+ strictEqual(popcount32(0b10110100), 4);
16
+ });
17
+
18
+ it("uses uint32 semantics", () => {
19
+ strictEqual(popcount32(-1), 32);
20
+ strictEqual(popcount32(0xffffffff), 32);
21
+ });
22
+ });
23
+
24
+ describe("popcount64", () => {
25
+ it("counts set bits for bigint values", () => {
26
+ strictEqual(popcount64(0n), 0);
27
+ strictEqual(popcount64((1n << 64n) - 1n), 64);
28
+ strictEqual(popcount64(0b101001n), 3);
29
+ });
30
+
31
+ it("counts only low 64 bits", () => {
32
+ const value = (1n << 65n) | 1n;
33
+ strictEqual(popcount64(value), 1);
34
+ });
35
+
36
+ it("throws for invalid number input", () => {
37
+ throws(() => popcount64(-1), /must be a non-negative safe integer/);
38
+ throws(() => popcount64(Number.MAX_SAFE_INTEGER + 1), /must be a non-negative safe integer/);
39
+ });
40
+ });
41
+
42
+ describe("countOnes", () => {
43
+ it("counts set bits for number and bigint", () => {
44
+ strictEqual(countOnes(0), 0);
45
+ strictEqual(countOnes(255), 8);
46
+ strictEqual(countOnes(0b10000000000000001n), 2);
47
+ });
48
+ });
49
+
50
+ describe("countZeroesWithWidth", () => {
51
+ it("counts zero bits for a chosen width", () => {
52
+ strictEqual(countZeroesWithWidth(0b10100, 5), 3);
53
+ strictEqual(countZeroesWithWidth(0b10100, 8), 6);
54
+ strictEqual(countZeroesWithWidth(0, 8), 8);
55
+ strictEqual(countZeroesWithWidth(123, 0), 0);
56
+ });
57
+
58
+ it("throws for invalid width", () => {
59
+ throws(() => countZeroesWithWidth(1, -1), /width must be a non-negative safe integer/);
60
+ throws(() => countZeroesWithWidth(1, Number.MAX_SAFE_INTEGER + 1), /width must be a non-negative safe integer/);
61
+ });
62
+ });
63
+
64
+ describe("countZeroes", () => {
65
+ it("counts zero bits only up to highest set bit", () => {
66
+ strictEqual(countZeroes(0), 0);
67
+ strictEqual(countZeroes(1), 0);
68
+ strictEqual(countZeroes(0b10100), 3);
69
+ strictEqual(countZeroes(0b1000), 3);
70
+ strictEqual(countZeroes(0b1111), 0);
71
+ });
72
+
73
+ it("throws for invalid inputs", () => {
74
+ throws(() => countZeroes(-1), /must be a non-negative safe integer/);
75
+ throws(() => countOnes(-1n), /must be non-negative/);
76
+ });
77
+ });