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.
- package/LICENSE +201 -0
- package/README.md +534 -0
- package/package.json +101 -0
- package/src/array/array-utils-creation.test.mts +443 -0
- package/src/array/array-utils-modification.test.mts +197 -0
- package/src/array/array-utils-overload-type-error.test.mts +149 -0
- package/src/array/array-utils-reducing-value.test.mts +425 -0
- package/src/array/array-utils-search.test.mts +169 -0
- package/src/array/array-utils-set-op.test.mts +335 -0
- package/src/array/array-utils-slice-clamped.test.mts +113 -0
- package/src/array/array-utils-slicing.test.mts +316 -0
- package/src/array/array-utils-transformation.test.mts +790 -0
- package/src/array/array-utils-validation.test.mts +492 -0
- package/src/array/array-utils.mts +4000 -0
- package/src/array/array.test.mts +146 -0
- package/src/array/index.mts +2 -0
- package/src/array/tuple-utils.mts +519 -0
- package/src/array/tuple-utils.test.mts +518 -0
- package/src/collections/imap-mapped.mts +801 -0
- package/src/collections/imap-mapped.test.mts +860 -0
- package/src/collections/imap.mts +651 -0
- package/src/collections/imap.test.mts +932 -0
- package/src/collections/index.mts +6 -0
- package/src/collections/iset-mapped.mts +889 -0
- package/src/collections/iset-mapped.test.mts +1187 -0
- package/src/collections/iset.mts +682 -0
- package/src/collections/iset.test.mts +1084 -0
- package/src/collections/queue.mts +390 -0
- package/src/collections/queue.test.mts +282 -0
- package/src/collections/stack.mts +423 -0
- package/src/collections/stack.test.mts +225 -0
- package/src/expect-type.mts +206 -0
- package/src/functional/index.mts +4 -0
- package/src/functional/match.mts +300 -0
- package/src/functional/match.test.mts +177 -0
- package/src/functional/optional.mts +733 -0
- package/src/functional/optional.test.mts +619 -0
- package/src/functional/pipe.mts +212 -0
- package/src/functional/pipe.test.mts +85 -0
- package/src/functional/result.mts +1134 -0
- package/src/functional/result.test.mts +777 -0
- package/src/globals.d.mts +38 -0
- package/src/guard/has-key.mts +119 -0
- package/src/guard/has-key.test.mts +219 -0
- package/src/guard/index.mts +7 -0
- package/src/guard/is-non-empty-string.mts +108 -0
- package/src/guard/is-non-empty-string.test.mts +91 -0
- package/src/guard/is-non-null-object.mts +106 -0
- package/src/guard/is-non-null-object.test.mts +90 -0
- package/src/guard/is-primitive.mts +165 -0
- package/src/guard/is-primitive.test.mts +102 -0
- package/src/guard/is-record.mts +153 -0
- package/src/guard/is-record.test.mts +112 -0
- package/src/guard/is-type.mts +450 -0
- package/src/guard/is-type.test.mts +496 -0
- package/src/guard/key-is-in.mts +163 -0
- package/src/guard/key-is-in.test.mts +19 -0
- package/src/index.mts +10 -0
- package/src/iterator/index.mts +1 -0
- package/src/iterator/range.mts +120 -0
- package/src/iterator/range.test.mts +33 -0
- package/src/json/index.mts +1 -0
- package/src/json/json.mts +711 -0
- package/src/json/json.test.mts +628 -0
- package/src/number/branded-types/finite-number.mts +354 -0
- package/src/number/branded-types/finite-number.test.mts +135 -0
- package/src/number/branded-types/index.mts +26 -0
- package/src/number/branded-types/int.mts +278 -0
- package/src/number/branded-types/int.test.mts +140 -0
- package/src/number/branded-types/int16.mts +192 -0
- package/src/number/branded-types/int16.test.mts +170 -0
- package/src/number/branded-types/int32.mts +193 -0
- package/src/number/branded-types/int32.test.mts +170 -0
- package/src/number/branded-types/non-negative-finite-number.mts +223 -0
- package/src/number/branded-types/non-negative-finite-number.test.mts +188 -0
- package/src/number/branded-types/non-negative-int16.mts +187 -0
- package/src/number/branded-types/non-negative-int16.test.mts +201 -0
- package/src/number/branded-types/non-negative-int32.mts +187 -0
- package/src/number/branded-types/non-negative-int32.test.mts +204 -0
- package/src/number/branded-types/non-zero-finite-number.mts +229 -0
- package/src/number/branded-types/non-zero-finite-number.test.mts +198 -0
- package/src/number/branded-types/non-zero-int.mts +167 -0
- package/src/number/branded-types/non-zero-int.test.mts +177 -0
- package/src/number/branded-types/non-zero-int16.mts +196 -0
- package/src/number/branded-types/non-zero-int16.test.mts +195 -0
- package/src/number/branded-types/non-zero-int32.mts +196 -0
- package/src/number/branded-types/non-zero-int32.test.mts +197 -0
- package/src/number/branded-types/non-zero-safe-int.mts +196 -0
- package/src/number/branded-types/non-zero-safe-int.test.mts +232 -0
- package/src/number/branded-types/non-zero-uint16.mts +189 -0
- package/src/number/branded-types/non-zero-uint16.test.mts +199 -0
- package/src/number/branded-types/non-zero-uint32.mts +189 -0
- package/src/number/branded-types/non-zero-uint32.test.mts +199 -0
- package/src/number/branded-types/positive-finite-number.mts +241 -0
- package/src/number/branded-types/positive-finite-number.test.mts +204 -0
- package/src/number/branded-types/positive-int.mts +304 -0
- package/src/number/branded-types/positive-int.test.mts +176 -0
- package/src/number/branded-types/positive-int16.mts +188 -0
- package/src/number/branded-types/positive-int16.test.mts +197 -0
- package/src/number/branded-types/positive-int32.mts +188 -0
- package/src/number/branded-types/positive-int32.test.mts +197 -0
- package/src/number/branded-types/positive-safe-int.mts +187 -0
- package/src/number/branded-types/positive-safe-int.test.mts +210 -0
- package/src/number/branded-types/positive-uint16.mts +188 -0
- package/src/number/branded-types/positive-uint16.test.mts +203 -0
- package/src/number/branded-types/positive-uint32.mts +188 -0
- package/src/number/branded-types/positive-uint32.test.mts +203 -0
- package/src/number/branded-types/safe-int.mts +291 -0
- package/src/number/branded-types/safe-int.test.mts +170 -0
- package/src/number/branded-types/safe-uint.mts +187 -0
- package/src/number/branded-types/safe-uint.test.mts +176 -0
- package/src/number/branded-types/uint.mts +179 -0
- package/src/number/branded-types/uint.test.mts +158 -0
- package/src/number/branded-types/uint16.mts +186 -0
- package/src/number/branded-types/uint16.test.mts +170 -0
- package/src/number/branded-types/uint32.mts +218 -0
- package/src/number/branded-types/uint32.test.mts +170 -0
- package/src/number/enum/index.mts +2 -0
- package/src/number/enum/int8.mts +344 -0
- package/src/number/enum/int8.test.mts +180 -0
- package/src/number/enum/uint8.mts +293 -0
- package/src/number/enum/uint8.test.mts +164 -0
- package/src/number/index.mts +4 -0
- package/src/number/num.mts +604 -0
- package/src/number/num.test.mts +242 -0
- package/src/number/refined-number-utils.mts +566 -0
- package/src/object/index.mts +1 -0
- package/src/object/object.mts +447 -0
- package/src/object/object.test.mts +124 -0
- package/src/others/cast-mutable.mts +113 -0
- package/src/others/cast-readonly.mts +192 -0
- package/src/others/cast-readonly.test.mts +89 -0
- package/src/others/if-then.mts +98 -0
- package/src/others/if-then.test.mts +75 -0
- package/src/others/index.mts +7 -0
- package/src/others/map-nullable.mts +172 -0
- package/src/others/map-nullable.test.mts +297 -0
- package/src/others/memoize-function.mts +196 -0
- package/src/others/memoize-function.test.mts +168 -0
- package/src/others/tuple.mts +160 -0
- package/src/others/tuple.test.mts +11 -0
- package/src/others/unknown-to-string.mts +215 -0
- 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
|
+
}
|