ts-data-forge 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.
Files changed (143) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +534 -0
  3. package/package.json +101 -0
  4. package/src/array/array-utils-creation.test.mts +443 -0
  5. package/src/array/array-utils-modification.test.mts +197 -0
  6. package/src/array/array-utils-overload-type-error.test.mts +149 -0
  7. package/src/array/array-utils-reducing-value.test.mts +425 -0
  8. package/src/array/array-utils-search.test.mts +169 -0
  9. package/src/array/array-utils-set-op.test.mts +335 -0
  10. package/src/array/array-utils-slice-clamped.test.mts +113 -0
  11. package/src/array/array-utils-slicing.test.mts +316 -0
  12. package/src/array/array-utils-transformation.test.mts +790 -0
  13. package/src/array/array-utils-validation.test.mts +492 -0
  14. package/src/array/array-utils.mts +4000 -0
  15. package/src/array/array.test.mts +146 -0
  16. package/src/array/index.mts +2 -0
  17. package/src/array/tuple-utils.mts +519 -0
  18. package/src/array/tuple-utils.test.mts +518 -0
  19. package/src/collections/imap-mapped.mts +801 -0
  20. package/src/collections/imap-mapped.test.mts +860 -0
  21. package/src/collections/imap.mts +651 -0
  22. package/src/collections/imap.test.mts +932 -0
  23. package/src/collections/index.mts +6 -0
  24. package/src/collections/iset-mapped.mts +889 -0
  25. package/src/collections/iset-mapped.test.mts +1187 -0
  26. package/src/collections/iset.mts +682 -0
  27. package/src/collections/iset.test.mts +1084 -0
  28. package/src/collections/queue.mts +390 -0
  29. package/src/collections/queue.test.mts +282 -0
  30. package/src/collections/stack.mts +423 -0
  31. package/src/collections/stack.test.mts +225 -0
  32. package/src/expect-type.mts +206 -0
  33. package/src/functional/index.mts +4 -0
  34. package/src/functional/match.mts +300 -0
  35. package/src/functional/match.test.mts +177 -0
  36. package/src/functional/optional.mts +733 -0
  37. package/src/functional/optional.test.mts +619 -0
  38. package/src/functional/pipe.mts +212 -0
  39. package/src/functional/pipe.test.mts +85 -0
  40. package/src/functional/result.mts +1134 -0
  41. package/src/functional/result.test.mts +777 -0
  42. package/src/globals.d.mts +38 -0
  43. package/src/guard/has-key.mts +119 -0
  44. package/src/guard/has-key.test.mts +219 -0
  45. package/src/guard/index.mts +7 -0
  46. package/src/guard/is-non-empty-string.mts +108 -0
  47. package/src/guard/is-non-empty-string.test.mts +91 -0
  48. package/src/guard/is-non-null-object.mts +106 -0
  49. package/src/guard/is-non-null-object.test.mts +90 -0
  50. package/src/guard/is-primitive.mts +165 -0
  51. package/src/guard/is-primitive.test.mts +102 -0
  52. package/src/guard/is-record.mts +153 -0
  53. package/src/guard/is-record.test.mts +112 -0
  54. package/src/guard/is-type.mts +450 -0
  55. package/src/guard/is-type.test.mts +496 -0
  56. package/src/guard/key-is-in.mts +163 -0
  57. package/src/guard/key-is-in.test.mts +19 -0
  58. package/src/index.mts +10 -0
  59. package/src/iterator/index.mts +1 -0
  60. package/src/iterator/range.mts +120 -0
  61. package/src/iterator/range.test.mts +33 -0
  62. package/src/json/index.mts +1 -0
  63. package/src/json/json.mts +711 -0
  64. package/src/json/json.test.mts +628 -0
  65. package/src/number/branded-types/finite-number.mts +354 -0
  66. package/src/number/branded-types/finite-number.test.mts +135 -0
  67. package/src/number/branded-types/index.mts +26 -0
  68. package/src/number/branded-types/int.mts +278 -0
  69. package/src/number/branded-types/int.test.mts +140 -0
  70. package/src/number/branded-types/int16.mts +192 -0
  71. package/src/number/branded-types/int16.test.mts +170 -0
  72. package/src/number/branded-types/int32.mts +193 -0
  73. package/src/number/branded-types/int32.test.mts +170 -0
  74. package/src/number/branded-types/non-negative-finite-number.mts +223 -0
  75. package/src/number/branded-types/non-negative-finite-number.test.mts +188 -0
  76. package/src/number/branded-types/non-negative-int16.mts +187 -0
  77. package/src/number/branded-types/non-negative-int16.test.mts +201 -0
  78. package/src/number/branded-types/non-negative-int32.mts +187 -0
  79. package/src/number/branded-types/non-negative-int32.test.mts +204 -0
  80. package/src/number/branded-types/non-zero-finite-number.mts +229 -0
  81. package/src/number/branded-types/non-zero-finite-number.test.mts +198 -0
  82. package/src/number/branded-types/non-zero-int.mts +167 -0
  83. package/src/number/branded-types/non-zero-int.test.mts +177 -0
  84. package/src/number/branded-types/non-zero-int16.mts +196 -0
  85. package/src/number/branded-types/non-zero-int16.test.mts +195 -0
  86. package/src/number/branded-types/non-zero-int32.mts +196 -0
  87. package/src/number/branded-types/non-zero-int32.test.mts +197 -0
  88. package/src/number/branded-types/non-zero-safe-int.mts +196 -0
  89. package/src/number/branded-types/non-zero-safe-int.test.mts +232 -0
  90. package/src/number/branded-types/non-zero-uint16.mts +189 -0
  91. package/src/number/branded-types/non-zero-uint16.test.mts +199 -0
  92. package/src/number/branded-types/non-zero-uint32.mts +189 -0
  93. package/src/number/branded-types/non-zero-uint32.test.mts +199 -0
  94. package/src/number/branded-types/positive-finite-number.mts +241 -0
  95. package/src/number/branded-types/positive-finite-number.test.mts +204 -0
  96. package/src/number/branded-types/positive-int.mts +304 -0
  97. package/src/number/branded-types/positive-int.test.mts +176 -0
  98. package/src/number/branded-types/positive-int16.mts +188 -0
  99. package/src/number/branded-types/positive-int16.test.mts +197 -0
  100. package/src/number/branded-types/positive-int32.mts +188 -0
  101. package/src/number/branded-types/positive-int32.test.mts +197 -0
  102. package/src/number/branded-types/positive-safe-int.mts +187 -0
  103. package/src/number/branded-types/positive-safe-int.test.mts +210 -0
  104. package/src/number/branded-types/positive-uint16.mts +188 -0
  105. package/src/number/branded-types/positive-uint16.test.mts +203 -0
  106. package/src/number/branded-types/positive-uint32.mts +188 -0
  107. package/src/number/branded-types/positive-uint32.test.mts +203 -0
  108. package/src/number/branded-types/safe-int.mts +291 -0
  109. package/src/number/branded-types/safe-int.test.mts +170 -0
  110. package/src/number/branded-types/safe-uint.mts +187 -0
  111. package/src/number/branded-types/safe-uint.test.mts +176 -0
  112. package/src/number/branded-types/uint.mts +179 -0
  113. package/src/number/branded-types/uint.test.mts +158 -0
  114. package/src/number/branded-types/uint16.mts +186 -0
  115. package/src/number/branded-types/uint16.test.mts +170 -0
  116. package/src/number/branded-types/uint32.mts +218 -0
  117. package/src/number/branded-types/uint32.test.mts +170 -0
  118. package/src/number/enum/index.mts +2 -0
  119. package/src/number/enum/int8.mts +344 -0
  120. package/src/number/enum/int8.test.mts +180 -0
  121. package/src/number/enum/uint8.mts +293 -0
  122. package/src/number/enum/uint8.test.mts +164 -0
  123. package/src/number/index.mts +4 -0
  124. package/src/number/num.mts +604 -0
  125. package/src/number/num.test.mts +242 -0
  126. package/src/number/refined-number-utils.mts +566 -0
  127. package/src/object/index.mts +1 -0
  128. package/src/object/object.mts +447 -0
  129. package/src/object/object.test.mts +124 -0
  130. package/src/others/cast-mutable.mts +113 -0
  131. package/src/others/cast-readonly.mts +192 -0
  132. package/src/others/cast-readonly.test.mts +89 -0
  133. package/src/others/if-then.mts +98 -0
  134. package/src/others/if-then.test.mts +75 -0
  135. package/src/others/index.mts +7 -0
  136. package/src/others/map-nullable.mts +172 -0
  137. package/src/others/map-nullable.test.mts +297 -0
  138. package/src/others/memoize-function.mts +196 -0
  139. package/src/others/memoize-function.test.mts +168 -0
  140. package/src/others/tuple.mts +160 -0
  141. package/src/others/tuple.test.mts +11 -0
  142. package/src/others/unknown-to-string.mts +215 -0
  143. package/src/others/unknown-to-string.test.mts +114 -0
@@ -0,0 +1,604 @@
1
+ import { expectType } from '../expect-type.mjs';
2
+
3
+ /**
4
+ * Namespace providing utility functions for number manipulation and validation.
5
+ *
6
+ * This namespace offers a comprehensive set of type-safe number utilities including:
7
+ * - Type conversion and validation
8
+ * - Type guards for numeric constraints (non-zero, non-negative, positive)
9
+ * - Range checking and clamping operations
10
+ * - Mathematical operations with type safety
11
+ * - Rounding utilities
12
+ *
13
+ * Many functions in this namespace leverage TypeScript's type system to provide
14
+ * compile-time guarantees about numeric constraints.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * // Type conversion
19
+ * const num = Num.from('123.45'); // 123.45
20
+ * const invalid = Num.from('abc'); // NaN
21
+ *
22
+ * // Type guards
23
+ * const value = 5;
24
+ * if (Num.isPositive(value)) {
25
+ * // value is typed as PositiveNumber & 5
26
+ * }
27
+ *
28
+ * // Range checking
29
+ * const isValid = Num.isInRange(0, 100)(50); // true
30
+ *
31
+ * // Clamping
32
+ * const clamped = Num.clamp(150, 0, 100); // 100
33
+ * const clampFn = Num.clamp(0, 100);
34
+ * const result = clampFn(150); // 100
35
+ * ```
36
+ */
37
+ export namespace Num {
38
+ /**
39
+ * Converts an unknown value to a number. Alias for the `Number` constructor.
40
+ * @param n The value to convert.
41
+ * @returns The numeric representation of `n`.
42
+ * @example
43
+ * ```typescript
44
+ * Num.from('123'); // 123
45
+ * Num.from('123.45'); // 123.45
46
+ * Num.from(true); // 1
47
+ * Num.from(false); // 0
48
+ * Num.from('hello'); // NaN
49
+ * ```
50
+ */
51
+ export const from: (n: unknown) => number = Number;
52
+
53
+ /**
54
+ * Type guard that checks if a number is non-zero.
55
+ *
56
+ * When this function returns `true`, TypeScript narrows the type to exclude zero,
57
+ * providing compile-time safety for division operations and other calculations
58
+ * that require non-zero values.
59
+ *
60
+ * @template N - The numeric literal type or number type to check
61
+ * @param num - The number to check
62
+ * @returns `true` if the number is not zero, `false` otherwise
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * const value = 5;
67
+ * if (Num.isNonZero(value)) {
68
+ * // value is typed as NonZeroNumber & 5
69
+ * const result = 10 / value; // Safe division
70
+ * }
71
+ *
72
+ * // Works with numeric literals
73
+ * const literal = 0 as 0 | 1 | 2;
74
+ * if (Num.isNonZero(literal)) {
75
+ * // literal is typed as 1 | 2
76
+ * }
77
+ * ```
78
+ */
79
+ export const isNonZero = <N extends number>(
80
+ num: N,
81
+ ): num is NonZeroNumber & RelaxedExclude<N, 0> => num !== 0;
82
+
83
+ expectType<NonZeroNumber & RelaxedExclude<123, 0>, UnknownBrand>('<=');
84
+
85
+ /**
86
+ * Type guard that checks if a number is non-negative (greater than or equal to zero).
87
+ *
88
+ * When this function returns `true`, TypeScript narrows the type to exclude negative
89
+ * values, which is useful for operations that require non-negative inputs like
90
+ * array indices or measurements.
91
+ *
92
+ * @template N - The numeric literal type or number type to check
93
+ * @param num - The number to check
94
+ * @returns `true` if the number is >= 0, `false` otherwise
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * const value = 10;
99
+ * if (Num.isNonNegative(value)) {
100
+ * // value is typed as NonNegativeNumber & 10
101
+ * const arr = new Array(value); // Safe array creation
102
+ * }
103
+ *
104
+ * // Type narrowing with unions
105
+ * const index = -1 as -1 | 0 | 1;
106
+ * if (Num.isNonNegative(index)) {
107
+ * // index is typed as 0 | 1
108
+ * }
109
+ * ```
110
+ */
111
+ export const isNonNegative = <N extends number>(
112
+ num: N,
113
+ ): num is NonNegativeNumber & RelaxedExclude<N, NegativeIndex<1024>> =>
114
+ num >= 0;
115
+
116
+ /**
117
+ * Type guard that checks if a number is positive (greater than zero).
118
+ *
119
+ * When this function returns `true`, TypeScript narrows the type to exclude zero
120
+ * and negative values. This is particularly useful for validating inputs that
121
+ * must be strictly positive, such as dimensions, counts, or rates.
122
+ *
123
+ * @template N - The numeric literal type or number type to check
124
+ * @param num - The number to check
125
+ * @returns `true` if the number is > 0, `false` otherwise
126
+ *
127
+ * @example
128
+ * ```typescript
129
+ * const count = 5;
130
+ * if (Num.isPositive(count)) {
131
+ * // count is typed as PositiveNumber & 5
132
+ * const average = total / count; // Safe division
133
+ * }
134
+ *
135
+ * // Type narrowing with numeric literals
136
+ * const value = 0 as -1 | 0 | 1 | 2;
137
+ * if (Num.isPositive(value)) {
138
+ * // value is typed as 1 | 2
139
+ * }
140
+ * ```
141
+ */
142
+ export const isPositive = <N extends number>(
143
+ num: N,
144
+ ): num is PositiveNumber & RelaxedExclude<N, NegativeIndex<1024> | 0> =>
145
+ num > 0;
146
+
147
+ /**
148
+ * Creates a function that checks if a number `x` is within the range `lowerBound <= x < upperBound`.
149
+ * @param lowerBound The lower bound (inclusive).
150
+ * @param upperBound The upper bound (exclusive).
151
+ * @returns A function that takes a number `x` and returns `true` if `x` is in the range, `false` otherwise.
152
+ * @example
153
+ * ```typescript
154
+ * const isInRange0to10 = Num.isInRange(0, 10);
155
+ * isInRange0to10(5); // true
156
+ * isInRange0to10(0); // true (inclusive lower bound)
157
+ * isInRange0to10(10); // false (exclusive upper bound)
158
+ * isInRange0to10(-1); // false
159
+ * ```
160
+ */
161
+ export const isInRange =
162
+ (lowerBound: number, upperBound: number) =>
163
+ (x: number): boolean =>
164
+ lowerBound <= x && x < upperBound;
165
+
166
+ /**
167
+ * Creates a function that checks if a number `x` is within the range `lowerBound <= x <= upperBound`.
168
+ * @param lowerBound The lower bound (inclusive).
169
+ * @param upperBound The upper bound (inclusive).
170
+ * @returns A function that takes a number `x` and returns `true` if `x` is in the range, `false` otherwise.
171
+ * @example
172
+ * ```typescript
173
+ * const inRange = Num.isInRangeInclusive(1, 10);
174
+ * console.log(inRange(1)); // true (lower bound)
175
+ * console.log(inRange(5)); // true
176
+ * console.log(inRange(10)); // true (upper bound)
177
+ * console.log(inRange(11)); // false
178
+ * ```
179
+ */
180
+ export const isInRangeInclusive =
181
+ (lowerBound: number, upperBound: number) =>
182
+ (x: number): boolean =>
183
+ lowerBound <= x && x <= upperBound;
184
+
185
+ /**
186
+ * @internal
187
+ * Helper type mapping each SmallUint N to the union of integers from 0 to N-1.
188
+ * Used internally for type-safe range operations.
189
+ *
190
+ * For example:
191
+ * - LT[3] = 0 | 1 | 2
192
+ * - LT[5] = 0 | 1 | 2 | 3 | 4
193
+ *
194
+ * @template N - A SmallUint representing the exclusive upper bound
195
+ */
196
+ type LT = {
197
+ [N in SmallUint]: Index<N>;
198
+ };
199
+
200
+ /**
201
+ * @internal
202
+ * Helper type mapping each SmallUint N to the union of integers from 0 to N (inclusive).
203
+ * Used internally for type-safe range operations with inclusive upper bounds.
204
+ *
205
+ * For example:
206
+ * - LEQ[3] = 0 | 1 | 2 | 3
207
+ * - LEQ[5] = 0 | 1 | 2 | 3 | 4 | 5
208
+ *
209
+ * @template N - A SmallUint representing the inclusive upper bound
210
+ */
211
+ type LEQ = {
212
+ [N in SmallUint]: Index<N> | N;
213
+ };
214
+
215
+ /**
216
+ * Creates a type guard that checks if a number is an unsigned integer within a specified range.
217
+ *
218
+ * This function returns a predicate that validates whether a number is:
219
+ * - A safe integer (no floating point)
220
+ * - Within the range [lowerBound, upperBound)
221
+ *
222
+ * The returned type guard provides precise type narrowing when the bounds are
223
+ * SmallUint literals, making it ideal for array index validation.
224
+ *
225
+ * @template L - The lower bound as a SmallUint literal type
226
+ * @template U - The upper bound as a SmallUint literal type
227
+ * @param lowerBound - The minimum value (inclusive)
228
+ * @param upperBound - The maximum value (exclusive)
229
+ * @returns A type guard function that validates and narrows number types
230
+ *
231
+ * @example
232
+ * ```typescript
233
+ * // Array index validation
234
+ * const isValidIndex = Num.isUintInRange(0, 10);
235
+ * const index: number = getUserInput();
236
+ *
237
+ * if (isValidIndex(index)) {
238
+ * // index is typed as 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
239
+ * const value = array[index]; // Safe array access
240
+ * }
241
+ *
242
+ * // Custom range validation
243
+ * const isValidPercentage = Num.isUintInRange(0, 101);
244
+ * if (isValidPercentage(value)) {
245
+ * // value is typed as 0 | 1 | ... | 100
246
+ * }
247
+ * ```
248
+ */
249
+ export const isUintInRange =
250
+ <L extends SmallUint, U extends SmallUint>(lowerBound: L, upperBound: U) =>
251
+ (x: number): x is RelaxedExclude<LT[U], LT[Min<L>]> =>
252
+ Number.isSafeInteger(x) && lowerBound <= x && x < upperBound;
253
+
254
+ /**
255
+ * Creates a type guard that checks if a number is an unsigned integer within a specified inclusive range.
256
+ *
257
+ * This function returns a predicate that validates whether a number is:
258
+ * - A safe integer (no floating point)
259
+ * - Within the range [lowerBound, upperBound] (both bounds inclusive)
260
+ *
261
+ * The returned type guard provides precise type narrowing when the bounds are
262
+ * SmallUint literals, useful for validating scores, percentages, or other bounded values.
263
+ *
264
+ * @template L - The lower bound as a SmallUint literal type
265
+ * @template U - The upper bound as a SmallUint literal type
266
+ * @param lowerBound - The minimum value (inclusive)
267
+ * @param upperBound - The maximum value (inclusive)
268
+ * @returns A type guard function that validates and narrows number types
269
+ *
270
+ * @example
271
+ * ```typescript
272
+ * // Score validation (0-100)
273
+ * const isValidScore = Num.isUintInRangeInclusive(0, 100);
274
+ * const score: number = getTestScore();
275
+ *
276
+ * if (isValidScore(score)) {
277
+ * // score is typed as 0 | 1 | 2 | ... | 100
278
+ * const grade = calculateGrade(score);
279
+ * }
280
+ *
281
+ * // Day of month validation
282
+ * const isValidDay = Num.isUintInRangeInclusive(1, 31);
283
+ * if (isValidDay(day)) {
284
+ * // day is typed as 1 | 2 | ... | 31
285
+ * }
286
+ * ```
287
+ */
288
+ export const isUintInRangeInclusive =
289
+ <L extends SmallUint, U extends SmallUint>(lowerBound: L, upperBound: U) =>
290
+ (x: number): x is RelaxedExclude<LEQ[U], LT[Min<L>]> =>
291
+ Number.isSafeInteger(x) && lowerBound <= x && x <= upperBound;
292
+
293
+ /**
294
+ * Clamps a value within the given range. If the target value is invalid (not finite), returns the lower bound.
295
+ *
296
+ * Provides two usage patterns for maximum flexibility:
297
+ * - **Direct usage**: Pass all three arguments to get the clamped value immediately
298
+ * - **Curried usage**: Pass bounds to get a reusable clamping function
299
+ *
300
+ * @example
301
+ * Direct usage:
302
+ * ```typescript
303
+ * Num.clamp(15, 0, 10); // 10 (clamped to upper bound)
304
+ * Num.clamp(-5, 0, 10); // 0 (clamped to lower bound)
305
+ * Num.clamp(5, 0, 10); // 5 (within bounds)
306
+ * Num.clamp(NaN, 0, 10); // 0 (invalid values default to lower bound)
307
+ * ```
308
+ *
309
+ * @example
310
+ * Curried usage for reusable functions:
311
+ * ```typescript
312
+ * const clampToPercent = Num.clamp(0, 100);
313
+ * clampToPercent(150); // 100
314
+ * clampToPercent(-10); // 0
315
+ * clampToPercent(75); // 75
316
+ *
317
+ * // Perfect for pipe composition
318
+ * const result = pipe(userInput)
319
+ * .map(Number)
320
+ * .map(clampToPercent).value;
321
+ * ```
322
+ *
323
+ * @example
324
+ * Working with arrays and functional programming:
325
+ * ```typescript
326
+ * const clampTo0_1 = Num.clamp(0, 1);
327
+ * const normalizedValues = values.map(clampTo0_1);
328
+ *
329
+ * // Temperature clamping
330
+ * const clampTemperature = Num.clamp(-40, 50);
331
+ * const safeTemperatures = readings.map(clampTemperature);
332
+ * ```
333
+ */
334
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
335
+ export const clamp: ClampFnOverload = ((
336
+ ...args:
337
+ | readonly [target: number, lowerBound: number, upperBound: number]
338
+ | readonly [lowerBound: number, upperBound: number]
339
+ ) => {
340
+ switch (args.length) {
341
+ case 3: {
342
+ const [target, lowerBound, upperBound] = args;
343
+ return !Number.isFinite(target)
344
+ ? lowerBound
345
+ : Math.max(lowerBound, Math.min(upperBound, target));
346
+ }
347
+
348
+ case 2: {
349
+ const [lowerBound, upperBound] = args;
350
+ return (target: number): number =>
351
+ clamp(target, lowerBound, upperBound);
352
+ }
353
+ }
354
+ }) as ClampFnOverload;
355
+
356
+ type ClampFnOverload = {
357
+ (target: number, lowerBound: number, upperBound: number): number;
358
+
359
+ // Curried version
360
+ (lowerBound: number, upperBound: number): (target: number) => number;
361
+ };
362
+
363
+ /**
364
+ * Performs type-safe division with compile-time zero-check.
365
+ *
366
+ * This function leverages TypeScript's type system to prevent division by zero
367
+ * at compile time. The divisor must be typed as NonZeroNumber or a non-zero
368
+ * numeric literal.
369
+ *
370
+ * @param a - The dividend
371
+ * @param b - The divisor (must be non-zero, enforced by types)
372
+ * @returns The quotient of a / b
373
+ *
374
+ * @example
375
+ * ```typescript
376
+ * // Safe division with literals
377
+ * const result1 = Num.div(10, 2); // 5
378
+ * const result2 = Num.div(7, 3); // 2.3333...
379
+ *
380
+ * // Compile-time error prevention
381
+ * // Num.div(10, 0); // ❌ TypeScript error: Type '0' is not assignable
382
+ *
383
+ * // With type guards
384
+ * const divisor: number = getDivisor();
385
+ * if (Num.isNonZero(divisor)) {
386
+ * const result = Num.div(100, divisor); // ✅ Safe
387
+ * }
388
+ *
389
+ * // With branded types
390
+ * const nonZero = asNonZeroNumber(5);
391
+ * const result3 = Num.div(20, nonZero); // 4
392
+ * ```
393
+ */
394
+ export const div = (a: number, b: NonZeroNumber | SmallInt<'!=0'>): number =>
395
+ a / b;
396
+
397
+ /**
398
+ * Performs integer division using floor division.
399
+ *
400
+ * Computes `⌊a / b⌋` by flooring both operands before division and then
401
+ * flooring the result. This ensures integer arithmetic semantics.
402
+ *
403
+ * Note: Unlike `div`, this function does not enforce non-zero divisor at
404
+ * compile time. Division by zero returns `NaN`.
405
+ *
406
+ * @param a - The dividend
407
+ * @param b - The divisor
408
+ * @returns The integer quotient, or `NaN` if b is zero
409
+ *
410
+ * @example
411
+ * ```typescript
412
+ * Num.divInt(10, 3); // 3
413
+ * Num.divInt(10, -3); // -4 (floor division)
414
+ * Num.divInt(-10, 3); // -4
415
+ * Num.divInt(10.7, 3.2); // 3 (floors both inputs first)
416
+ * Num.divInt(10, 0); // NaN
417
+ * ```
418
+ */
419
+ export const divInt = (a: number, b: number): number =>
420
+ Math.floor(Math.floor(a) / Math.floor(b));
421
+
422
+ /**
423
+ * Rounds a number to a specified number of decimal places.
424
+ *
425
+ * Uses the standard rounding algorithm (round half up) to round the number
426
+ * to the given precision. The precision must be a positive safe integer.
427
+ *
428
+ * @param num - The number to round
429
+ * @param precision - The number of decimal places (must be positive)
430
+ * @returns The rounded number
431
+ *
432
+ * @example
433
+ * ```typescript
434
+ * Num.roundAt(3.14159, 2); // 3.14
435
+ * Num.roundAt(3.14159, 4); // 3.1416
436
+ * Num.roundAt(10.5, 0); // 11
437
+ * Num.roundAt(-10.5, 0); // -10
438
+ * Num.roundAt(0.005, 2); // 0.01
439
+ * ```
440
+ */
441
+ export const roundAt = (
442
+ num: number,
443
+ precision: PositiveSafeIntWithSmallInt,
444
+ ): number => {
445
+ const digit = 10 ** precision;
446
+
447
+ return Math.round(num * digit) / digit;
448
+ };
449
+
450
+ /**
451
+ * Rounds a number to the nearest integer using bitwise operations.
452
+ *
453
+ * This function uses a bitwise OR trick for potentially faster rounding.
454
+ * Note: This implementation rounds half up for positive numbers but may
455
+ * behave differently for negative numbers compared to Math.round.
456
+ *
457
+ * @param num - The number to round
458
+ * @returns The rounded integer as an Int branded type
459
+ *
460
+ * @example
461
+ * ```typescript
462
+ * Num.roundToInt(3.2); // 3
463
+ * Num.roundToInt(3.5); // 4
464
+ * Num.roundToInt(3.8); // 4
465
+ * Num.roundToInt(-3.2); // -3
466
+ * Num.roundToInt(-3.8); // -3
467
+ * ```
468
+ */
469
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
470
+ export const roundToInt = (num: number): Int => (0 | (num + 0.5)) as Int;
471
+
472
+ /**
473
+ * Creates a reusable rounding function with a fixed precision.
474
+ *
475
+ * This is a curried version of roundAt that returns a function configured
476
+ * to always round to the specified number of decimal places. Useful for
477
+ * creating consistent rounding behavior across multiple values.
478
+ *
479
+ * @param digit - The number of decimal places for rounding
480
+ * @returns A function that rounds numbers to the specified precision
481
+ *
482
+ * @example
483
+ * ```typescript
484
+ * // Create specialized rounding functions
485
+ * const roundTo2 = Num.round(2);
486
+ * const roundTo4 = Num.round(4);
487
+ *
488
+ * roundTo2(3.14159); // 3.14
489
+ * roundTo2(2.71828); // 2.72
490
+ * roundTo2(10); // 10
491
+ *
492
+ * roundTo4(3.14159); // 3.1416
493
+ *
494
+ * // Use with array operations
495
+ * const values = [1.234, 5.678, 9.012];
496
+ * const rounded = values.map(roundTo2); // [1.23, 5.68, 9.01]
497
+ * ```
498
+ */
499
+ export const round = (
500
+ digit: PositiveSafeIntWithSmallInt,
501
+ ): ((num: number) => number) => {
502
+ const powAmount = 10 ** digit;
503
+
504
+ return (target: number) => roundToInt(powAmount * target) / powAmount;
505
+ };
506
+
507
+ /**
508
+ * Converts NaN values to undefined while preserving all other numbers.
509
+ *
510
+ * This function is useful for handling potentially invalid numeric operations
511
+ * in a type-safe way, converting NaN results to undefined for easier handling
512
+ * with optional chaining or nullish coalescing.
513
+ *
514
+ * @template N - The numeric type (literal or number)
515
+ * @param num - The number to check
516
+ * @returns The original number if not NaN, otherwise undefined
517
+ *
518
+ * @example
519
+ * ```typescript
520
+ * Num.mapNaN2Undefined(42); // 42
521
+ * Num.mapNaN2Undefined(0); // 0
522
+ * Num.mapNaN2Undefined(NaN); // undefined
523
+ * Num.mapNaN2Undefined(Math.sqrt(-1)); // undefined
524
+ *
525
+ * // Useful in chains
526
+ * const result = Num.mapNaN2Undefined(parseFloat(userInput)) ?? 0;
527
+ *
528
+ * // Type narrowing
529
+ * const value = Math.sqrt(x);
530
+ * const safe = Num.mapNaN2Undefined(value);
531
+ * if (safe !== undefined) {
532
+ * // safe is typed without NaN
533
+ * }
534
+ * ```
535
+ */
536
+ export const mapNaN2Undefined = <N extends number>(
537
+ num: N,
538
+ ): RelaxedExclude<N, NaNType> | undefined =>
539
+ Number.isNaN(num)
540
+ ? undefined
541
+ : // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
542
+ (num as RelaxedExclude<N, NaNType>);
543
+
544
+ /**
545
+ * Type-safe increment operation for SmallUint values.
546
+ *
547
+ * Increments a SmallUint (0-40) by 1 with the result type computed at
548
+ * compile time. This provides type-level arithmetic for small unsigned
549
+ * integers, useful for type-safe counter operations.
550
+ *
551
+ * @template N - A SmallUint literal type (0-40)
552
+ * @param n - The SmallUint value to increment
553
+ * @returns The incremented value with type Increment<N>
554
+ *
555
+ * @example
556
+ * ```typescript
557
+ * const zero = 0 as 0;
558
+ * const one = Num.increment(zero); // type is 1, value is 1
559
+ *
560
+ * const five = 5 as 5;
561
+ * const six = Num.increment(five); // type is 6, value is 6
562
+ *
563
+ * // Type-safe counter
564
+ * type Counter<N extends SmallUint> = {
565
+ * value: N;
566
+ * next(): Counter<Increment<N>>;
567
+ * };
568
+ * ```
569
+ */
570
+ export const increment = <N extends SmallUint>(n: N): Increment<N> =>
571
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
572
+ (n + 1) as Increment<N>;
573
+
574
+ /**
575
+ * Type-safe decrement operation for positive SmallInt values.
576
+ *
577
+ * Decrements a positive SmallInt (1-40) by 1 with the result type computed
578
+ * at compile time. This provides type-level arithmetic for small positive
579
+ * integers, useful for type-safe countdown operations.
580
+ *
581
+ * @template N - A positive SmallInt literal type (1-40)
582
+ * @param n - The positive SmallInt value to decrement
583
+ * @returns The decremented value with type Decrement<N>
584
+ *
585
+ * @example
586
+ * ```typescript
587
+ * const three = 3 as 3;
588
+ * const two = Num.decrement(three); // type is 2, value is 2
589
+ *
590
+ * const one = 1 as 1;
591
+ * const zero = Num.decrement(one); // type is 0, value is 0
592
+ *
593
+ * // Type-safe countdown
594
+ * function countdown<N extends PositiveSmallInt>(
595
+ * n: N
596
+ * ): N extends 1 ? 0 : Decrement<N> {
597
+ * return Num.decrement(n);
598
+ * }
599
+ * ```
600
+ */
601
+ export const decrement = <N extends PositiveSmallInt>(n: N): Decrement<N> =>
602
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
603
+ (n - 1) as Decrement<N>;
604
+ }