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.
- package/README.md +2 -2
- package/dist/array/impl/array-utils-iterators.mjs +6 -0
- package/dist/array/impl/array-utils-iterators.mjs.map +1 -1
- package/dist/array/impl/array-utils-modification.mjs +6 -0
- package/dist/array/impl/array-utils-modification.mjs.map +1 -1
- package/dist/array/impl/array-utils-reducing-value.mjs +6 -0
- package/dist/array/impl/array-utils-reducing-value.mjs.map +1 -1
- package/dist/array/impl/array-utils-search.mjs +6 -0
- package/dist/array/impl/array-utils-search.mjs.map +1 -1
- package/dist/array/impl/array-utils-slice-clamped.mjs +6 -0
- package/dist/array/impl/array-utils-slice-clamped.mjs.map +1 -1
- package/dist/array/impl/array-utils-slicing.mjs +6 -0
- package/dist/array/impl/array-utils-slicing.mjs.map +1 -1
- package/dist/array/impl/array-utils-transformation.mjs +6 -0
- package/dist/array/impl/array-utils-transformation.mjs.map +1 -1
- package/dist/array/impl/array-utils-validation.mjs +6 -0
- package/dist/array/impl/array-utils-validation.mjs.map +1 -1
- package/dist/collections/imap-mapped.mjs +6 -0
- package/dist/collections/imap-mapped.mjs.map +1 -1
- package/dist/collections/imap.mjs +6 -0
- package/dist/collections/imap.mjs.map +1 -1
- package/dist/collections/iset-mapped.mjs +6 -0
- package/dist/collections/iset-mapped.mjs.map +1 -1
- package/dist/collections/iset.mjs +6 -0
- package/dist/collections/iset.mjs.map +1 -1
- package/dist/collections/queue.mjs +6 -0
- package/dist/collections/queue.mjs.map +1 -1
- package/dist/collections/stack.mjs +6 -0
- package/dist/collections/stack.mjs.map +1 -1
- package/dist/entry-point.mjs +6 -0
- package/dist/entry-point.mjs.map +1 -1
- package/dist/functional/pipe.d.mts +6 -18
- package/dist/functional/pipe.d.mts.map +1 -1
- package/dist/functional/pipe.mjs +35 -15
- package/dist/functional/pipe.mjs.map +1 -1
- package/dist/index.mjs +6 -0
- package/dist/index.mjs.map +1 -1
- package/dist/iterator/range.mjs +6 -0
- package/dist/iterator/range.mjs.map +1 -1
- package/dist/json/json.mjs +6 -0
- package/dist/json/json.mjs.map +1 -1
- package/dist/number/branded-types/finite-number.d.mts +1 -1
- package/dist/number/branded-types/index.d.mts +6 -0
- package/dist/number/branded-types/index.d.mts.map +1 -1
- package/dist/number/branded-types/index.mjs +6 -0
- package/dist/number/branded-types/index.mjs.map +1 -1
- package/dist/number/branded-types/int.d.mts +1 -2
- package/dist/number/branded-types/int.d.mts.map +1 -1
- package/dist/number/branded-types/int16.d.mts +2 -3
- package/dist/number/branded-types/int16.d.mts.map +1 -1
- package/dist/number/branded-types/int32.d.mts +2 -3
- package/dist/number/branded-types/int32.d.mts.map +1 -1
- package/dist/number/branded-types/negative-finite-number.d.mts +143 -0
- package/dist/number/branded-types/negative-finite-number.d.mts.map +1 -0
- package/dist/number/branded-types/negative-finite-number.mjs +156 -0
- package/dist/number/branded-types/negative-finite-number.mjs.map +1 -0
- package/dist/number/branded-types/negative-int.d.mts +120 -0
- package/dist/number/branded-types/negative-int.d.mts.map +1 -0
- package/dist/number/branded-types/negative-int.mjs +125 -0
- package/dist/number/branded-types/negative-int.mjs.map +1 -0
- package/dist/number/branded-types/negative-safe-int.d.mts +132 -0
- package/dist/number/branded-types/negative-safe-int.d.mts.map +1 -0
- package/dist/number/branded-types/negative-safe-int.mjs +136 -0
- package/dist/number/branded-types/negative-safe-int.mjs.map +1 -0
- package/dist/number/branded-types/non-negative-finite-number.d.mts +1 -1
- package/dist/number/branded-types/non-negative-int16.d.mts +1 -1
- package/dist/number/branded-types/non-negative-int32.d.mts +1 -1
- package/dist/number/branded-types/non-positive-finite-number.d.mts +130 -0
- package/dist/number/branded-types/non-positive-finite-number.d.mts.map +1 -0
- package/dist/number/branded-types/non-positive-finite-number.mjs +134 -0
- package/dist/number/branded-types/non-positive-finite-number.mjs.map +1 -0
- package/dist/number/branded-types/non-positive-int.d.mts +224 -0
- package/dist/number/branded-types/non-positive-int.d.mts.map +1 -0
- package/dist/number/branded-types/non-positive-int.mjs +222 -0
- package/dist/number/branded-types/non-positive-int.mjs.map +1 -0
- package/dist/number/branded-types/non-positive-safe-int.d.mts +119 -0
- package/dist/number/branded-types/non-positive-safe-int.d.mts.map +1 -0
- package/dist/number/branded-types/non-positive-safe-int.mjs +116 -0
- package/dist/number/branded-types/non-positive-safe-int.mjs.map +1 -0
- package/dist/number/branded-types/non-zero-finite-number.d.mts +5 -17
- package/dist/number/branded-types/non-zero-finite-number.d.mts.map +1 -1
- package/dist/number/branded-types/non-zero-finite-number.mjs +5 -17
- package/dist/number/branded-types/non-zero-finite-number.mjs.map +1 -1
- package/dist/number/branded-types/non-zero-int.d.mts +5 -33
- package/dist/number/branded-types/non-zero-int.d.mts.map +1 -1
- package/dist/number/branded-types/non-zero-int.mjs +5 -25
- package/dist/number/branded-types/non-zero-int.mjs.map +1 -1
- package/dist/number/branded-types/non-zero-int16.d.mts +6 -43
- package/dist/number/branded-types/non-zero-int16.d.mts.map +1 -1
- package/dist/number/branded-types/non-zero-int16.mjs +5 -25
- package/dist/number/branded-types/non-zero-int16.mjs.map +1 -1
- package/dist/number/branded-types/non-zero-int32.d.mts +6 -39
- package/dist/number/branded-types/non-zero-int32.d.mts.map +1 -1
- package/dist/number/branded-types/non-zero-int32.mjs +5 -25
- package/dist/number/branded-types/non-zero-int32.mjs.map +1 -1
- package/dist/number/branded-types/non-zero-safe-int.d.mts +5 -37
- package/dist/number/branded-types/non-zero-safe-int.d.mts.map +1 -1
- package/dist/number/branded-types/non-zero-safe-int.mjs +5 -28
- package/dist/number/branded-types/non-zero-safe-int.mjs.map +1 -1
- package/dist/number/branded-types/non-zero-uint16.d.mts +6 -33
- package/dist/number/branded-types/non-zero-uint16.d.mts.map +1 -1
- package/dist/number/branded-types/non-zero-uint16.mjs +5 -17
- package/dist/number/branded-types/non-zero-uint16.mjs.map +1 -1
- package/dist/number/branded-types/non-zero-uint32.d.mts +6 -31
- package/dist/number/branded-types/non-zero-uint32.d.mts.map +1 -1
- package/dist/number/branded-types/non-zero-uint32.mjs +5 -17
- package/dist/number/branded-types/non-zero-uint32.mjs.map +1 -1
- package/dist/number/branded-types/positive-finite-number.d.mts +4 -2
- package/dist/number/branded-types/positive-finite-number.d.mts.map +1 -1
- package/dist/number/branded-types/positive-int.d.mts +4 -2
- package/dist/number/branded-types/positive-int.d.mts.map +1 -1
- package/dist/number/branded-types/positive-int16.d.mts +4 -2
- package/dist/number/branded-types/positive-int16.d.mts.map +1 -1
- package/dist/number/branded-types/positive-int32.d.mts +4 -2
- package/dist/number/branded-types/positive-int32.d.mts.map +1 -1
- package/dist/number/branded-types/positive-safe-int.d.mts +4 -2
- package/dist/number/branded-types/positive-safe-int.d.mts.map +1 -1
- package/dist/number/branded-types/positive-uint16.d.mts +4 -2
- package/dist/number/branded-types/positive-uint16.d.mts.map +1 -1
- package/dist/number/branded-types/positive-uint32.d.mts +4 -2
- package/dist/number/branded-types/positive-uint32.d.mts.map +1 -1
- package/dist/number/branded-types/safe-int.d.mts +1 -2
- package/dist/number/branded-types/safe-int.d.mts.map +1 -1
- package/dist/number/branded-types/safe-uint.d.mts +1 -1
- package/dist/number/branded-types/uint.d.mts +1 -1
- package/dist/number/branded-types/uint16.d.mts +1 -1
- package/dist/number/branded-types/uint32.d.mts +1 -1
- package/dist/number/index.d.mts +1 -0
- package/dist/number/index.d.mts.map +1 -1
- package/dist/number/index.mjs +6 -0
- package/dist/number/index.mjs.map +1 -1
- package/dist/number/num-arithmetic-types.d.mts +74 -0
- package/dist/number/num-arithmetic-types.d.mts.map +1 -0
- package/dist/number/num-arithmetic-types.mjs +2 -0
- package/dist/number/num-arithmetic-types.mjs.map +1 -0
- package/dist/number/num.d.mts +53 -9
- package/dist/number/num.d.mts.map +1 -1
- package/dist/number/num.mjs +64 -12
- package/dist/number/num.mjs.map +1 -1
- package/dist/number/refined-number-utils.d.mts +12 -9
- package/dist/number/refined-number-utils.d.mts.map +1 -1
- package/dist/number/refined-number-utils.mjs.map +1 -1
- package/dist/object/object.mjs +6 -0
- package/dist/object/object.mjs.map +1 -1
- package/package.json +5 -2
- package/src/functional/pipe.mts +104 -78
- package/src/functional/pipe.test.mts +57 -1
- package/src/number/branded-types/index.mts +6 -0
- package/src/number/branded-types/negative-finite-number.mts +210 -0
- package/src/number/branded-types/negative-finite-number.test.mts +172 -0
- package/src/number/branded-types/negative-int.mts +160 -0
- package/src/number/branded-types/negative-int.test.mts +146 -0
- package/src/number/branded-types/negative-safe-int.mts +173 -0
- package/src/number/branded-types/negative-safe-int.test.mts +120 -0
- package/src/number/branded-types/non-positive-finite-number.mts +191 -0
- package/src/number/branded-types/non-positive-finite-number.test.mts +286 -0
- package/src/number/branded-types/non-positive-int.mts +251 -0
- package/src/number/branded-types/non-positive-int.test.mts +197 -0
- package/src/number/branded-types/non-positive-safe-int.mts +147 -0
- package/src/number/branded-types/non-positive-safe-int.test.mts +223 -0
- package/src/number/branded-types/non-zero-finite-number.mts +14 -22
- package/src/number/branded-types/non-zero-finite-number.test.mts +0 -12
- package/src/number/branded-types/non-zero-int.mts +10 -32
- package/src/number/branded-types/non-zero-int.test.mts +0 -20
- package/src/number/branded-types/non-zero-int16.mts +8 -32
- package/src/number/branded-types/non-zero-int16.test.mts +0 -30
- package/src/number/branded-types/non-zero-int32.mts +8 -32
- package/src/number/branded-types/non-zero-int32.test.mts +0 -30
- package/src/number/branded-types/non-zero-safe-int.mts +8 -35
- package/src/number/branded-types/non-zero-safe-int.test.mts +0 -32
- package/src/number/branded-types/non-zero-uint16.mts +8 -22
- package/src/number/branded-types/non-zero-uint16.test.mts +0 -16
- package/src/number/branded-types/non-zero-uint32.mts +8 -22
- package/src/number/branded-types/non-zero-uint32.test.mts +0 -16
- package/src/number/index.mts +1 -0
- package/src/number/num-arithmetic-types.mts +269 -0
- package/src/number/num-arithmetic-types.test.mts +130 -0
- package/src/number/num-arithmetic.test.mts +102 -0
- package/src/number/num.mts +96 -15
- package/src/number/refined-number-utils.mts +19 -9
- 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
|
+
});
|
package/src/number/num.mts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
510
|
-
*
|
|
511
|
-
*
|
|
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
|
|
514
|
-
* @param b - The
|
|
515
|
-
* @returns
|
|
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
|
|
518
|
-
|
|
519
|
-
|
|
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
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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.
|