ts-data-forge 6.13.0 → 7.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 (181) hide show
  1. package/README.md +2 -2
  2. package/dist/array/impl/array-utils-iterators.mjs +6 -0
  3. package/dist/array/impl/array-utils-iterators.mjs.map +1 -1
  4. package/dist/array/impl/array-utils-modification.mjs +6 -0
  5. package/dist/array/impl/array-utils-modification.mjs.map +1 -1
  6. package/dist/array/impl/array-utils-reducing-value.mjs +6 -0
  7. package/dist/array/impl/array-utils-reducing-value.mjs.map +1 -1
  8. package/dist/array/impl/array-utils-search.mjs +6 -0
  9. package/dist/array/impl/array-utils-search.mjs.map +1 -1
  10. package/dist/array/impl/array-utils-slice-clamped.mjs +6 -0
  11. package/dist/array/impl/array-utils-slice-clamped.mjs.map +1 -1
  12. package/dist/array/impl/array-utils-slicing.mjs +6 -0
  13. package/dist/array/impl/array-utils-slicing.mjs.map +1 -1
  14. package/dist/array/impl/array-utils-transformation.mjs +6 -0
  15. package/dist/array/impl/array-utils-transformation.mjs.map +1 -1
  16. package/dist/array/impl/array-utils-validation.mjs +6 -0
  17. package/dist/array/impl/array-utils-validation.mjs.map +1 -1
  18. package/dist/collections/imap-mapped.mjs +6 -0
  19. package/dist/collections/imap-mapped.mjs.map +1 -1
  20. package/dist/collections/imap.mjs +6 -0
  21. package/dist/collections/imap.mjs.map +1 -1
  22. package/dist/collections/iset-mapped.mjs +6 -0
  23. package/dist/collections/iset-mapped.mjs.map +1 -1
  24. package/dist/collections/iset.mjs +6 -0
  25. package/dist/collections/iset.mjs.map +1 -1
  26. package/dist/collections/queue.mjs +6 -0
  27. package/dist/collections/queue.mjs.map +1 -1
  28. package/dist/collections/stack.mjs +6 -0
  29. package/dist/collections/stack.mjs.map +1 -1
  30. package/dist/entry-point.mjs +6 -0
  31. package/dist/entry-point.mjs.map +1 -1
  32. package/dist/functional/pipe.d.mts +6 -18
  33. package/dist/functional/pipe.d.mts.map +1 -1
  34. package/dist/functional/pipe.mjs +35 -15
  35. package/dist/functional/pipe.mjs.map +1 -1
  36. package/dist/index.mjs +6 -0
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/iterator/range.mjs +6 -0
  39. package/dist/iterator/range.mjs.map +1 -1
  40. package/dist/json/json.mjs +6 -0
  41. package/dist/json/json.mjs.map +1 -1
  42. package/dist/number/branded-types/finite-number.d.mts +1 -1
  43. package/dist/number/branded-types/index.d.mts +6 -0
  44. package/dist/number/branded-types/index.d.mts.map +1 -1
  45. package/dist/number/branded-types/index.mjs +6 -0
  46. package/dist/number/branded-types/index.mjs.map +1 -1
  47. package/dist/number/branded-types/int.d.mts +1 -2
  48. package/dist/number/branded-types/int.d.mts.map +1 -1
  49. package/dist/number/branded-types/int16.d.mts +2 -3
  50. package/dist/number/branded-types/int16.d.mts.map +1 -1
  51. package/dist/number/branded-types/int32.d.mts +2 -3
  52. package/dist/number/branded-types/int32.d.mts.map +1 -1
  53. package/dist/number/branded-types/negative-finite-number.d.mts +143 -0
  54. package/dist/number/branded-types/negative-finite-number.d.mts.map +1 -0
  55. package/dist/number/branded-types/negative-finite-number.mjs +156 -0
  56. package/dist/number/branded-types/negative-finite-number.mjs.map +1 -0
  57. package/dist/number/branded-types/negative-int.d.mts +120 -0
  58. package/dist/number/branded-types/negative-int.d.mts.map +1 -0
  59. package/dist/number/branded-types/negative-int.mjs +125 -0
  60. package/dist/number/branded-types/negative-int.mjs.map +1 -0
  61. package/dist/number/branded-types/negative-safe-int.d.mts +132 -0
  62. package/dist/number/branded-types/negative-safe-int.d.mts.map +1 -0
  63. package/dist/number/branded-types/negative-safe-int.mjs +136 -0
  64. package/dist/number/branded-types/negative-safe-int.mjs.map +1 -0
  65. package/dist/number/branded-types/non-negative-finite-number.d.mts +1 -1
  66. package/dist/number/branded-types/non-negative-int16.d.mts +1 -1
  67. package/dist/number/branded-types/non-negative-int32.d.mts +1 -1
  68. package/dist/number/branded-types/non-positive-finite-number.d.mts +130 -0
  69. package/dist/number/branded-types/non-positive-finite-number.d.mts.map +1 -0
  70. package/dist/number/branded-types/non-positive-finite-number.mjs +134 -0
  71. package/dist/number/branded-types/non-positive-finite-number.mjs.map +1 -0
  72. package/dist/number/branded-types/non-positive-int.d.mts +224 -0
  73. package/dist/number/branded-types/non-positive-int.d.mts.map +1 -0
  74. package/dist/number/branded-types/non-positive-int.mjs +222 -0
  75. package/dist/number/branded-types/non-positive-int.mjs.map +1 -0
  76. package/dist/number/branded-types/non-positive-safe-int.d.mts +119 -0
  77. package/dist/number/branded-types/non-positive-safe-int.d.mts.map +1 -0
  78. package/dist/number/branded-types/non-positive-safe-int.mjs +116 -0
  79. package/dist/number/branded-types/non-positive-safe-int.mjs.map +1 -0
  80. package/dist/number/branded-types/non-zero-finite-number.d.mts +5 -17
  81. package/dist/number/branded-types/non-zero-finite-number.d.mts.map +1 -1
  82. package/dist/number/branded-types/non-zero-finite-number.mjs +5 -17
  83. package/dist/number/branded-types/non-zero-finite-number.mjs.map +1 -1
  84. package/dist/number/branded-types/non-zero-int.d.mts +5 -33
  85. package/dist/number/branded-types/non-zero-int.d.mts.map +1 -1
  86. package/dist/number/branded-types/non-zero-int.mjs +5 -25
  87. package/dist/number/branded-types/non-zero-int.mjs.map +1 -1
  88. package/dist/number/branded-types/non-zero-int16.d.mts +6 -43
  89. package/dist/number/branded-types/non-zero-int16.d.mts.map +1 -1
  90. package/dist/number/branded-types/non-zero-int16.mjs +5 -25
  91. package/dist/number/branded-types/non-zero-int16.mjs.map +1 -1
  92. package/dist/number/branded-types/non-zero-int32.d.mts +6 -39
  93. package/dist/number/branded-types/non-zero-int32.d.mts.map +1 -1
  94. package/dist/number/branded-types/non-zero-int32.mjs +5 -25
  95. package/dist/number/branded-types/non-zero-int32.mjs.map +1 -1
  96. package/dist/number/branded-types/non-zero-safe-int.d.mts +5 -37
  97. package/dist/number/branded-types/non-zero-safe-int.d.mts.map +1 -1
  98. package/dist/number/branded-types/non-zero-safe-int.mjs +5 -28
  99. package/dist/number/branded-types/non-zero-safe-int.mjs.map +1 -1
  100. package/dist/number/branded-types/non-zero-uint16.d.mts +6 -33
  101. package/dist/number/branded-types/non-zero-uint16.d.mts.map +1 -1
  102. package/dist/number/branded-types/non-zero-uint16.mjs +5 -17
  103. package/dist/number/branded-types/non-zero-uint16.mjs.map +1 -1
  104. package/dist/number/branded-types/non-zero-uint32.d.mts +6 -31
  105. package/dist/number/branded-types/non-zero-uint32.d.mts.map +1 -1
  106. package/dist/number/branded-types/non-zero-uint32.mjs +5 -17
  107. package/dist/number/branded-types/non-zero-uint32.mjs.map +1 -1
  108. package/dist/number/branded-types/positive-finite-number.d.mts +4 -2
  109. package/dist/number/branded-types/positive-finite-number.d.mts.map +1 -1
  110. package/dist/number/branded-types/positive-int.d.mts +4 -2
  111. package/dist/number/branded-types/positive-int.d.mts.map +1 -1
  112. package/dist/number/branded-types/positive-int16.d.mts +4 -2
  113. package/dist/number/branded-types/positive-int16.d.mts.map +1 -1
  114. package/dist/number/branded-types/positive-int32.d.mts +4 -2
  115. package/dist/number/branded-types/positive-int32.d.mts.map +1 -1
  116. package/dist/number/branded-types/positive-safe-int.d.mts +4 -2
  117. package/dist/number/branded-types/positive-safe-int.d.mts.map +1 -1
  118. package/dist/number/branded-types/positive-uint16.d.mts +4 -2
  119. package/dist/number/branded-types/positive-uint16.d.mts.map +1 -1
  120. package/dist/number/branded-types/positive-uint32.d.mts +4 -2
  121. package/dist/number/branded-types/positive-uint32.d.mts.map +1 -1
  122. package/dist/number/branded-types/safe-int.d.mts +1 -2
  123. package/dist/number/branded-types/safe-int.d.mts.map +1 -1
  124. package/dist/number/branded-types/safe-uint.d.mts +1 -1
  125. package/dist/number/branded-types/uint.d.mts +1 -1
  126. package/dist/number/branded-types/uint16.d.mts +1 -1
  127. package/dist/number/branded-types/uint32.d.mts +1 -1
  128. package/dist/number/index.d.mts +1 -0
  129. package/dist/number/index.d.mts.map +1 -1
  130. package/dist/number/index.mjs +6 -0
  131. package/dist/number/index.mjs.map +1 -1
  132. package/dist/number/num-arithmetic-types.d.mts +74 -0
  133. package/dist/number/num-arithmetic-types.d.mts.map +1 -0
  134. package/dist/number/num-arithmetic-types.mjs +2 -0
  135. package/dist/number/num-arithmetic-types.mjs.map +1 -0
  136. package/dist/number/num.d.mts +53 -9
  137. package/dist/number/num.d.mts.map +1 -1
  138. package/dist/number/num.mjs +64 -12
  139. package/dist/number/num.mjs.map +1 -1
  140. package/dist/number/refined-number-utils.d.mts +12 -9
  141. package/dist/number/refined-number-utils.d.mts.map +1 -1
  142. package/dist/number/refined-number-utils.mjs.map +1 -1
  143. package/dist/object/object.mjs +6 -0
  144. package/dist/object/object.mjs.map +1 -1
  145. package/package.json +5 -2
  146. package/src/functional/pipe.mts +104 -78
  147. package/src/functional/pipe.test.mts +57 -1
  148. package/src/number/branded-types/index.mts +6 -0
  149. package/src/number/branded-types/negative-finite-number.mts +210 -0
  150. package/src/number/branded-types/negative-finite-number.test.mts +172 -0
  151. package/src/number/branded-types/negative-int.mts +160 -0
  152. package/src/number/branded-types/negative-int.test.mts +146 -0
  153. package/src/number/branded-types/negative-safe-int.mts +173 -0
  154. package/src/number/branded-types/negative-safe-int.test.mts +120 -0
  155. package/src/number/branded-types/non-positive-finite-number.mts +191 -0
  156. package/src/number/branded-types/non-positive-finite-number.test.mts +286 -0
  157. package/src/number/branded-types/non-positive-int.mts +251 -0
  158. package/src/number/branded-types/non-positive-int.test.mts +197 -0
  159. package/src/number/branded-types/non-positive-safe-int.mts +147 -0
  160. package/src/number/branded-types/non-positive-safe-int.test.mts +223 -0
  161. package/src/number/branded-types/non-zero-finite-number.mts +14 -22
  162. package/src/number/branded-types/non-zero-finite-number.test.mts +0 -12
  163. package/src/number/branded-types/non-zero-int.mts +10 -32
  164. package/src/number/branded-types/non-zero-int.test.mts +0 -20
  165. package/src/number/branded-types/non-zero-int16.mts +8 -32
  166. package/src/number/branded-types/non-zero-int16.test.mts +0 -30
  167. package/src/number/branded-types/non-zero-int32.mts +8 -32
  168. package/src/number/branded-types/non-zero-int32.test.mts +0 -30
  169. package/src/number/branded-types/non-zero-safe-int.mts +8 -35
  170. package/src/number/branded-types/non-zero-safe-int.test.mts +0 -32
  171. package/src/number/branded-types/non-zero-uint16.mts +8 -22
  172. package/src/number/branded-types/non-zero-uint16.test.mts +0 -16
  173. package/src/number/branded-types/non-zero-uint32.mts +8 -22
  174. package/src/number/branded-types/non-zero-uint32.test.mts +0 -16
  175. package/src/number/index.mts +1 -0
  176. package/src/number/num-arithmetic-types.mts +269 -0
  177. package/src/number/num-arithmetic-types.test.mts +130 -0
  178. package/src/number/num-arithmetic.test.mts +102 -0
  179. package/src/number/num.mts +96 -15
  180. package/src/number/refined-number-utils.mts +19 -9
  181. package/src/others/map-nullable.test.mts +3 -4
@@ -0,0 +1,269 @@
1
+ import {
2
+ type FiniteNumber,
3
+ type Int,
4
+ type NegativeFiniteNumber,
5
+ type NegativeInt,
6
+ type NegativeNumber,
7
+ type NegativeSafeInt,
8
+ type NonNegativeFiniteNumber,
9
+ type NonNegativeNumber,
10
+ type NonPositiveFiniteNumber,
11
+ type NonPositiveInt,
12
+ type NonPositiveNumber,
13
+ type NonPositiveSafeInt,
14
+ type NonZeroFiniteNumber,
15
+ type NonZeroInt,
16
+ type NonZeroNumber,
17
+ type NonZeroSafeInt,
18
+ type PositiveFiniteNumber,
19
+ type PositiveInt,
20
+ type PositiveNumber,
21
+ type PositiveSafeInt,
22
+ type SafeInt,
23
+ type SafeUint,
24
+ type Uint,
25
+ } from 'ts-type-forge';
26
+
27
+ /**
28
+ * Internal type-level arithmetic for {@link Num}'s `add`/`sub`/`mul`/`div`.
29
+ *
30
+ * The result type of a binary operation is computed from two independent axes of
31
+ * each operand:
32
+ *
33
+ * - **Sign** — `pos | neg | nonneg | nonpos | nonzero | any`, derived from the
34
+ * brand's sign keys and combined with a small boolean algebra over the facts
35
+ * `>= 0`, `<= 0`, `!= 0`.
36
+ * - **Level** — `safeint | int | finite | number`, derived from the brand's
37
+ * level keys.
38
+ *
39
+ * The sign is always computed exactly (so e.g. `neg * neg` becomes `pos`). The
40
+ * level widens on possible overflow (`safeint` results of `+`/`-`/`*` become
41
+ * `int`, since the magnitude may exceed the safe-integer range). No runtime
42
+ * clamping is performed — saturating "stay in the type" behavior is provided by
43
+ * the branded namespaces instead.
44
+ *
45
+ * @internal
46
+ */
47
+
48
+ /** Sign classification of a numeric brand. */
49
+ type Sign = 'any' | 'neg' | 'nonneg' | 'nonpos' | 'nonzero' | 'pos';
50
+
51
+ /** Level (integer/finite) classification of a numeric brand. */
52
+ type Level = 'finite' | 'int' | 'number' | 'safeint';
53
+
54
+ /** Classifies a numeric type's sign. */
55
+ // transformer-ignore-next-line convert-to-readonly
56
+ export type SignOf<N> = [N] extends [PositiveNumber]
57
+ ? 'pos'
58
+ : [N] extends [NegativeNumber]
59
+ ? 'neg'
60
+ : [N] extends [NonNegativeNumber]
61
+ ? 'nonneg'
62
+ : [N] extends [NonPositiveNumber]
63
+ ? 'nonpos'
64
+ : [N] extends [NonZeroNumber]
65
+ ? 'nonzero'
66
+ : 'any';
67
+
68
+ /** Classifies a numeric type's level. */
69
+ // transformer-ignore-next-line convert-to-readonly
70
+ export type LevelOf<N> = [N] extends [SafeInt]
71
+ ? 'safeint'
72
+ : [N] extends [Int]
73
+ ? 'int'
74
+ : [N] extends [FiniteNumber]
75
+ ? 'finite'
76
+ : 'number';
77
+
78
+ // --- boolean fact algebra over a sign ----------------------------------------
79
+
80
+ // transformer-ignore-next-line convert-to-readonly
81
+ type And<A extends boolean, B extends boolean> = [A, B] extends [true, true]
82
+ ? true
83
+ : false;
84
+
85
+ type Or<A extends boolean, B extends boolean> = true extends A | B
86
+ ? true
87
+ : false;
88
+
89
+ /** Whether the value is guaranteed `>= 0`. */
90
+ type Ge0<S extends Sign> = S extends 'pos' | 'nonneg' ? true : false;
91
+
92
+ /** Whether the value is guaranteed `<= 0`. */
93
+ type Le0<S extends Sign> = S extends 'neg' | 'nonpos' ? true : false;
94
+
95
+ /** Whether the value is guaranteed `!= 0`. */
96
+ type Ne0<S extends Sign> = S extends 'pos' | 'neg' | 'nonzero' ? true : false;
97
+
98
+ /** Rebuilds a {@link Sign} from the three facts. */
99
+ type FromFacts<
100
+ ge0 extends boolean,
101
+ le0 extends boolean,
102
+ ne0 extends boolean,
103
+ > = ge0 extends true
104
+ ? ne0 extends true
105
+ ? 'pos'
106
+ : 'nonneg'
107
+ : le0 extends true
108
+ ? ne0 extends true
109
+ ? 'neg'
110
+ : 'nonpos'
111
+ : ne0 extends true
112
+ ? 'nonzero'
113
+ : 'any';
114
+
115
+ type NegateSign<S extends Sign> = FromFacts<Le0<S>, Ge0<S>, Ne0<S>>;
116
+
117
+ // --- sign combination per operation ------------------------------------------
118
+
119
+ type AddSign<A extends Sign, B extends Sign> = FromFacts<
120
+ And<Ge0<A>, Ge0<B>>,
121
+ And<Le0<A>, Le0<B>>,
122
+ Or<
123
+ And<And<Ge0<A>, Ge0<B>>, Or<Ne0<A>, Ne0<B>>>,
124
+ And<And<Le0<A>, Le0<B>>, Or<Ne0<A>, Ne0<B>>>
125
+ >
126
+ >;
127
+
128
+ type SubSign<A extends Sign, B extends Sign> = AddSign<A, NegateSign<B>>;
129
+
130
+ /** Sign of a product/quotient, parameterized by the `!= 0` fact of the result. */
131
+ type MulSignWithNe0<
132
+ A extends Sign,
133
+ B extends Sign,
134
+ ne0 extends boolean,
135
+ > = FromFacts<
136
+ Or<And<Ge0<A>, Ge0<B>>, And<Le0<A>, Le0<B>>>,
137
+ Or<And<Ge0<A>, Le0<B>>, And<Le0<A>, Ge0<B>>>,
138
+ ne0
139
+ >;
140
+
141
+ type MulSign<A extends Sign, B extends Sign> = MulSignWithNe0<
142
+ A,
143
+ B,
144
+ And<Ne0<A>, Ne0<B>>
145
+ >;
146
+
147
+ /** Exact division: nonzero dividend over nonzero divisor stays nonzero. */
148
+ type DivSign<A extends Sign, B extends Sign> = MulSignWithNe0<A, B, Ne0<A>>;
149
+
150
+ /** Floor division: flooring can land on `0`, so the result is never `!= 0`. */
151
+ type DivIntSign<A extends Sign, B extends Sign> = MulSignWithNe0<A, B, false>;
152
+
153
+ // --- level combination per operation -----------------------------------------
154
+
155
+ /** Looser of two levels (number > finite > int > safeint). */
156
+ type LooserLevel<LA extends Level, LB extends Level> = 'number' extends LA | LB
157
+ ? 'number'
158
+ : 'finite' extends LA | LB
159
+ ? 'finite'
160
+ : 'int' extends LA | LB
161
+ ? 'int'
162
+ : 'safeint';
163
+
164
+ /** `+`/`-`/`*`: looser level, widening `safeint -> int` (overflow may occur). */
165
+ type AddMulLevel<LA extends Level, LB extends Level> =
166
+ LooserLevel<LA, LB> extends 'safeint' ? 'int' : LooserLevel<LA, LB>;
167
+
168
+ /** Exact division yields a (possibly fractional) finite number. */
169
+ type DivLevel<LA extends Level, LB extends Level> = 'number' extends LA | LB
170
+ ? 'number'
171
+ : 'finite';
172
+
173
+ /** Floor division yields an integer; the safe range is preserved. */
174
+ // transformer-ignore-next-line convert-to-readonly
175
+ type DivIntLevel<LA extends Level, LB extends Level> = 'number' extends LA | LB
176
+ ? 'number'
177
+ : [LA, LB] extends ['safeint', 'safeint']
178
+ ? 'safeint'
179
+ : 'int';
180
+
181
+ // --- reconstruction (sign, level) -> canonical brand -------------------------
182
+
183
+ type ReconstructNumber<S extends Sign> = S extends 'pos'
184
+ ? PositiveNumber
185
+ : S extends 'neg'
186
+ ? NegativeNumber
187
+ : S extends 'nonneg'
188
+ ? NonNegativeNumber
189
+ : S extends 'nonpos'
190
+ ? NonPositiveNumber
191
+ : S extends 'nonzero'
192
+ ? NonZeroNumber
193
+ : number;
194
+
195
+ type ReconstructFinite<S extends Sign> = S extends 'pos'
196
+ ? PositiveFiniteNumber
197
+ : S extends 'neg'
198
+ ? NegativeFiniteNumber
199
+ : S extends 'nonneg'
200
+ ? NonNegativeFiniteNumber
201
+ : S extends 'nonpos'
202
+ ? NonPositiveFiniteNumber
203
+ : S extends 'nonzero'
204
+ ? NonZeroFiniteNumber
205
+ : FiniteNumber;
206
+
207
+ type ReconstructInt<S extends Sign> = S extends 'pos'
208
+ ? PositiveInt
209
+ : S extends 'neg'
210
+ ? NegativeInt
211
+ : S extends 'nonneg'
212
+ ? Uint
213
+ : S extends 'nonpos'
214
+ ? NonPositiveInt
215
+ : S extends 'nonzero'
216
+ ? NonZeroInt
217
+ : Int;
218
+
219
+ type ReconstructSafeInt<S extends Sign> = S extends 'pos'
220
+ ? PositiveSafeInt
221
+ : S extends 'neg'
222
+ ? NegativeSafeInt
223
+ : S extends 'nonneg'
224
+ ? SafeUint
225
+ : S extends 'nonpos'
226
+ ? NonPositiveSafeInt
227
+ : S extends 'nonzero'
228
+ ? NonZeroSafeInt
229
+ : SafeInt;
230
+
231
+ type Reconstruct<S extends Sign, L extends Level> = L extends 'safeint'
232
+ ? ReconstructSafeInt<S>
233
+ : L extends 'int'
234
+ ? ReconstructInt<S>
235
+ : L extends 'finite'
236
+ ? ReconstructFinite<S>
237
+ : ReconstructNumber<S>;
238
+
239
+ // --- public result types -----------------------------------------------------
240
+
241
+ /** Result type of {@link Num.add} applied to operands of type `A` and `B`. */
242
+ export type AddResult<A, B> = Reconstruct<
243
+ AddSign<SignOf<A>, SignOf<B>>,
244
+ AddMulLevel<LevelOf<A>, LevelOf<B>>
245
+ >;
246
+
247
+ /** Result type of {@link Num.sub} applied to operands of type `A` and `B`. */
248
+ export type SubResult<A, B> = Reconstruct<
249
+ SubSign<SignOf<A>, SignOf<B>>,
250
+ AddMulLevel<LevelOf<A>, LevelOf<B>>
251
+ >;
252
+
253
+ /** Result type of {@link Num.mul} applied to operands of type `A` and `B`. */
254
+ export type MulResult<A, B> = Reconstruct<
255
+ MulSign<SignOf<A>, SignOf<B>>,
256
+ AddMulLevel<LevelOf<A>, LevelOf<B>>
257
+ >;
258
+
259
+ /** Result type of {@link Num.div} (exact) applied to operands of type `A` and `B`. */
260
+ export type DivResult<A, B> = Reconstruct<
261
+ DivSign<SignOf<A>, SignOf<B>>,
262
+ DivLevel<LevelOf<A>, LevelOf<B>>
263
+ >;
264
+
265
+ /** Result type of {@link Num.divInt} (floor) applied to operands of type `A` and `B`. */
266
+ export type DivIntResult<A, B> = Reconstruct<
267
+ DivIntSign<SignOf<A>, SignOf<B>>,
268
+ DivIntLevel<LevelOf<A>, LevelOf<B>>
269
+ >;
@@ -0,0 +1,130 @@
1
+ import {
2
+ type FiniteNumber,
3
+ type Int,
4
+ type NegativeFiniteNumber,
5
+ type NegativeInt,
6
+ type NonNegativeFiniteNumber,
7
+ type NonPositiveInt,
8
+ type NonZeroInt,
9
+ type PositiveFiniteNumber,
10
+ type PositiveInt,
11
+ type PositiveSafeInt,
12
+ type SafeUint,
13
+ type Uint,
14
+ } from 'ts-type-forge';
15
+ import { expectType } from '../expect-type.mjs';
16
+ import {
17
+ type AddResult,
18
+ type DivIntResult,
19
+ type DivResult,
20
+ type LevelOf,
21
+ type MulResult,
22
+ type SignOf,
23
+ type SubResult,
24
+ } from './num-arithmetic-types.mjs';
25
+
26
+ describe('NumericArithmetic type-level', () => {
27
+ test('SignOf', () => {
28
+ expectType<SignOf<PositiveInt>, 'pos'>('=');
29
+
30
+ expectType<SignOf<NegativeInt>, 'neg'>('=');
31
+
32
+ expectType<SignOf<Uint>, 'nonneg'>('=');
33
+
34
+ expectType<SignOf<NonPositiveInt>, 'nonpos'>('=');
35
+
36
+ expectType<SignOf<NonZeroInt>, 'nonzero'>('=');
37
+
38
+ expectType<SignOf<Int>, 'any'>('=');
39
+
40
+ expectType<SignOf<number>, 'any'>('=');
41
+ });
42
+
43
+ test('LevelOf', () => {
44
+ expectType<LevelOf<PositiveSafeInt>, 'safeint'>('=');
45
+
46
+ expectType<LevelOf<PositiveInt>, 'int'>('=');
47
+
48
+ expectType<LevelOf<PositiveFiniteNumber>, 'finite'>('=');
49
+
50
+ expectType<LevelOf<number>, 'number'>('=');
51
+ });
52
+
53
+ test('mul: sign flips', () => {
54
+ // <=0 * <=0 = >=0
55
+ expectType<MulResult<NonPositiveInt, NonPositiveInt>, Uint>('=');
56
+
57
+ // <0 * <0 = >0
58
+ expectType<MulResult<NegativeInt, NegativeInt>, PositiveInt>('=');
59
+
60
+ // >0 * <0 = <0
61
+ expectType<MulResult<PositiveInt, NegativeInt>, NegativeInt>('=');
62
+
63
+ // finite negatives -> positive finite
64
+ expectType<
65
+ MulResult<NegativeFiniteNumber, NegativeFiniteNumber>,
66
+ PositiveFiniteNumber
67
+ >('=');
68
+ });
69
+
70
+ test('mul: safeint widens to int', () => {
71
+ expectType<MulResult<PositiveSafeInt, PositiveSafeInt>, PositiveInt>('=');
72
+ });
73
+
74
+ test('add', () => {
75
+ expectType<AddResult<PositiveInt, PositiveInt>, PositiveInt>('=');
76
+
77
+ expectType<AddResult<Uint, Uint>, Uint>('=');
78
+
79
+ // positive + negative -> unknown sign
80
+ expectType<AddResult<PositiveInt, NegativeInt>, Int>('=');
81
+
82
+ // nonzero + nonzero -> can be 0
83
+ expectType<AddResult<NonZeroInt, NonZeroInt>, Int>('=');
84
+
85
+ // safeint widens
86
+ expectType<AddResult<SafeUint, SafeUint>, Uint>('=');
87
+ });
88
+
89
+ test('sub', () => {
90
+ // same-sign sub -> unknown sign (the saturating case lives in branded)
91
+ expectType<SubResult<Uint, Uint>, Int>('=');
92
+
93
+ expectType<SubResult<NonPositiveInt, NonPositiveInt>, Int>('=');
94
+
95
+ // >=0 - <=0 = >=0
96
+ expectType<SubResult<Uint, NonPositiveInt>, Uint>('=');
97
+ });
98
+
99
+ test('div (exact) -> finite level', () => {
100
+ expectType<DivResult<PositiveInt, PositiveInt>, PositiveFiniteNumber>('=');
101
+
102
+ expectType<
103
+ DivResult<NegativeFiniteNumber, NegativeFiniteNumber>,
104
+ PositiveFiniteNumber
105
+ >('=');
106
+
107
+ expectType<DivResult<NonPositiveInt, NegativeInt>, NonNegativeFiniteNumber>(
108
+ '=',
109
+ );
110
+ });
111
+
112
+ test('divInt (floor)', () => {
113
+ // <0 / <0 = >0 but floor can be 0 -> Uint
114
+ expectType<DivIntResult<NegativeInt, NegativeInt>, Uint>('=');
115
+
116
+ // >0 / >0 floor can be 0 -> Uint
117
+ expectType<DivIntResult<PositiveInt, PositiveInt>, Uint>('=');
118
+
119
+ // safeint preserved under floor division
120
+ expectType<DivIntResult<PositiveSafeInt, PositiveSafeInt>, SafeUint>('=');
121
+ });
122
+
123
+ test('plain number stays number', () => {
124
+ expectType<AddResult<number, number>, number>('=');
125
+
126
+ expectType<MulResult<number, PositiveInt>, number>('=');
127
+
128
+ expectType<AddResult<FiniteNumber, FiniteNumber>, FiniteNumber>('=');
129
+ });
130
+ });
@@ -0,0 +1,102 @@
1
+ import {
2
+ type Int,
3
+ type NegativeInt,
4
+ type PositiveFiniteNumber,
5
+ type PositiveInt,
6
+ type Uint,
7
+ } from 'ts-type-forge';
8
+ import { expectType } from '../expect-type.mjs';
9
+ import {
10
+ asNegativeInt,
11
+ asNonPositiveInt,
12
+ asPositiveInt,
13
+ asUint,
14
+ } from './branded-types/index.mjs';
15
+ import { Num } from './num.mjs';
16
+
17
+ describe('Num arithmetic (general / sign-aware)', () => {
18
+ describe('Num.add', () => {
19
+ test('adds and keeps the value (no saturation)', () => {
20
+ expect(Num.add(asPositiveInt(4), asPositiveInt(5))).toBe(9);
21
+ });
22
+
23
+ test('positive + negative is typed Int', () => {
24
+ const r = Num.add(asPositiveInt(4), asNegativeInt(-9));
25
+
26
+ expect(r).toBe(-5);
27
+
28
+ expectType<typeof r, Int>('=');
29
+ });
30
+
31
+ test('positive + positive stays PositiveInt', () => {
32
+ expectType<
33
+ ReturnType<typeof Num.add<PositiveInt, PositiveInt>>,
34
+ PositiveInt
35
+ >('=');
36
+ });
37
+ });
38
+
39
+ describe('Num.sub', () => {
40
+ test('does not saturate (unlike branded Uint.sub)', () => {
41
+ const r = Num.sub(asUint(3), asUint(5));
42
+
43
+ expect(r).toBe(-2);
44
+
45
+ expectType<typeof r, Int>('=');
46
+ });
47
+ });
48
+
49
+ describe('Num.mul', () => {
50
+ test('negative * negative is positive (PositiveInt)', () => {
51
+ const r = Num.mul(asNegativeInt(-5), asNegativeInt(-3));
52
+
53
+ expect(r).toBe(15);
54
+
55
+ expectType<typeof r, PositiveInt>('=');
56
+ });
57
+
58
+ test('positive * negative is negative (NegativeInt)', () => {
59
+ const r = Num.mul(asPositiveInt(5), asNegativeInt(-3));
60
+
61
+ expect(r).toBe(-15);
62
+
63
+ expectType<typeof r, NegativeInt>('=');
64
+ });
65
+
66
+ test('non-positive * non-positive is non-negative (Uint)', () => {
67
+ const r = Num.mul(asNonPositiveInt(-4), asNonPositiveInt(-2));
68
+
69
+ expect(r).toBe(8);
70
+
71
+ expectType<typeof r, Uint>('=');
72
+ });
73
+ });
74
+
75
+ describe('Num.div (exact)', () => {
76
+ test('exact division keeps the fractional value', () => {
77
+ const r = Num.div(asPositiveInt(9), asPositiveInt(2));
78
+
79
+ expect(r).toBe(4.5);
80
+
81
+ expectType<typeof r, PositiveFiniteNumber>('=');
82
+ });
83
+
84
+ test('negative / negative is positive', () => {
85
+ expect(Num.div(asNegativeInt(-10), asNegativeInt(-2))).toBe(5);
86
+ });
87
+ });
88
+
89
+ describe('Num.divInt (floor)', () => {
90
+ test('floor division of negatives is non-negative (Uint)', () => {
91
+ const r = Num.divInt(asNegativeInt(-7), asNegativeInt(-2));
92
+
93
+ expect(r).toBe(3);
94
+
95
+ expectType<typeof r, Uint>('=');
96
+ });
97
+
98
+ test('floor can reach 0', () => {
99
+ expect(Num.divInt(asNegativeInt(-2), asNegativeInt(-3))).toBe(0);
100
+ });
101
+ });
102
+ });
@@ -19,6 +19,13 @@ import {
19
19
  import { expectType } from '../expect-type.mjs';
20
20
  import { Result } from '../functional/index.mjs';
21
21
  import { type SmallPositiveInt } from '../types.mjs';
22
+ import {
23
+ type AddResult,
24
+ type DivIntResult,
25
+ type DivResult,
26
+ type MulResult,
27
+ type SubResult,
28
+ } from './num-arithmetic-types.mjs';
22
29
 
23
30
  /**
24
31
  * Namespace providing utility functions for number manipulation and validation.
@@ -504,19 +511,86 @@ export namespace Num {
504
511
  }
505
512
 
506
513
  /**
507
- * Performs type-safe division with compile-time zero-check.
514
+ * Adds two numbers, returning the tightest sound branded result type.
515
+ *
516
+ * The sign of the result is computed exactly (e.g. a non-negative plus a
517
+ * non-negative is non-negative); the integer/finite level is preserved, with
518
+ * `SafeInt` operands widening to `Int` because the sum may exceed the safe
519
+ * range. No clamping is performed — for a result that stays inside a bounded
520
+ * type, use that type's branded `add` instead.
521
+ *
522
+ * @param a - The first addend.
523
+ * @param b - The second addend.
524
+ * @returns `a + b`, typed as the tightest branded type guaranteed to contain
525
+ * it.
526
+ */
527
+ export const add = <A extends number, B extends number>(
528
+ a: A,
529
+ b: B,
530
+ ): AddResult<A, B> =>
531
+ // eslint-disable-next-line total-functions/no-unsafe-type-assertion
532
+ (a + b) as AddResult<A, B>;
533
+
534
+ /**
535
+ * Subtracts `b` from `a`, returning the tightest sound branded result type.
508
536
  *
509
- * This function leverages TypeScript's type system to prevent division by
510
- * zero at compile time. The divisor must be typed as NonZeroNumber or a
511
- * non-zero numeric literal.
537
+ * The sign is computed exactly; the level is preserved (with `SafeInt`
538
+ * widening to `Int`). No clamping — for saturating subtraction that stays in a
539
+ * bounded type (e.g. `Uint.sub` clamping at `0`), use that type's branded
540
+ * `sub`.
512
541
  *
513
- * @param a - The dividend
514
- * @param b - The divisor (must be non-zero, enforced by types)
515
- * @returns The quotient of a / b
542
+ * @param a - The minuend.
543
+ * @param b - The subtrahend.
544
+ * @returns `a - b`, typed as the tightest branded type guaranteed to contain
545
+ * it.
516
546
  */
517
- export const div = (a: number, b: NonZeroNumber | SmallInt<'!=0'>): number =>
518
- // eslint-disable-next-line total-functions/no-partial-division
519
- a / b;
547
+ export const sub = <A extends number, B extends number>(
548
+ a: A,
549
+ b: B,
550
+ ): SubResult<A, B> =>
551
+ // eslint-disable-next-line total-functions/no-unsafe-type-assertion
552
+ (a - b) as SubResult<A, B>;
553
+
554
+ /**
555
+ * Multiplies two numbers, returning the tightest sound branded result type.
556
+ *
557
+ * The sign follows the usual product rule (e.g. negative times negative is
558
+ * positive); the level is preserved (with `SafeInt` widening to `Int`). No
559
+ * clamping.
560
+ *
561
+ * @param a - The first factor.
562
+ * @param b - The second factor.
563
+ * @returns `a * b`, typed as the tightest branded type guaranteed to contain
564
+ * it.
565
+ */
566
+ export const mul = <A extends number, B extends number>(
567
+ a: A,
568
+ b: B,
569
+ ): MulResult<A, B> =>
570
+ // eslint-disable-next-line total-functions/no-unsafe-type-assertion
571
+ (a * b) as MulResult<A, B>;
572
+
573
+ /**
574
+ * Performs type-safe exact division with a compile-time zero-check.
575
+ *
576
+ * The divisor must be typed as `NonZeroNumber` or a non-zero numeric literal.
577
+ * The result sign follows the quotient rule; because exact division can be
578
+ * fractional, the level is `finite` (or `number`).
579
+ *
580
+ * @param a - The dividend.
581
+ * @param b - The divisor (must be non-zero, enforced by types).
582
+ * @returns `a / b`, typed as the tightest branded type guaranteed to contain
583
+ * it.
584
+ */
585
+ export const div = <
586
+ A extends number,
587
+ B extends NonZeroNumber | SmallInt<'!=0'>,
588
+ >(
589
+ a: A,
590
+ b: B,
591
+ ): DivResult<A, B> =>
592
+ // eslint-disable-next-line total-functions/no-partial-division, total-functions/no-unsafe-type-assertion
593
+ (a / b) as DivResult<A, B>;
520
594
 
521
595
  /**
522
596
  * Performs integer division using floor division.
@@ -531,12 +605,19 @@ export namespace Num {
531
605
  * @param b - The divisor
532
606
  * @returns The integer quotient, or `NaN` if b is zero
533
607
  */
534
- export const divInt = (
535
- a: number,
536
- b: NonZeroNumber | SmallInt<'!=0'>,
537
- ): number =>
608
+ export const divInt = <
609
+ A extends number,
610
+ B extends NonZeroNumber | SmallInt<'!=0'>,
611
+ >(
612
+ a: A,
613
+ b: B,
614
+ ): DivIntResult<A, B> => {
538
615
  // eslint-disable-next-line total-functions/no-partial-division
539
- Math.floor(Math.floor(a) / Math.floor(b));
616
+ const quotient = Math.floor(Math.floor(a) / Math.floor(b));
617
+
618
+ // eslint-disable-next-line total-functions/no-unsafe-type-assertion
619
+ return quotient as DivIntResult<A, B>;
620
+ };
540
621
 
541
622
  /**
542
623
  * Rounds a number to a specified number of decimal places.