Reputation: 21851
Is it possible from number type T
to get number type Y
that has value of T+1.
type one = 1
type Increment<T extends number> = ???
type two = Increment<one> // 2
P.S. Currently, I have hardcoded interface of incremented values, but the problem is hardcoded and hence limited:
export type IncrementMap = {
0: 1,
1: 2,
2: 3,
Upvotes: 10
Views: 7545
Reputation: 13
This solution has no range limit and works well with negative values BUT:
integers
IDE
due to its expensive calculation (That's why I used @ts-expect-error)
// Main type
type Increment<Num extends number> = _IsFloat<Num> extends true
? never
: _ToStr<Num> extends infer NumStr
? NumStr extends `-${infer PosNum extends number}`
? // @ts-expect-error - Expensive Calculation
_ToNum<`-${_Decrement<PosNum>}`>
: NumStr extends `${infer PosNum extends number}`
? _Increment<PosNum>
: never
: never;
// Helpers
type _IsFloat<Num extends number> = _ToStr<Num> extends `${infer Left}.${infer Right}` ? true : false;
type _Increment<PosNum extends number, ARR extends any[] = []> = PosNum extends ARR["length"]
? [...ARR, any]["length"]
: _Increment<PosNum, [...ARR, any]>;
type _Decrement<PosNum extends number, ARR extends any[] = []> = PosNum extends 0
? -1
: PosNum extends ARR["length"]
? ARR extends [infer First, ...infer Rest]
? Rest["length"]
: never
: _Decrement<PosNum, [...ARR, any]>;
type _ToStr<Type extends string | number | boolean | null | undefined | bigint> = `${Type}`;
type _ToNum<Str extends string> = Str extends `${infer Num extends number}` ? Num : never;
// Examples
type E1 = Increment<15>; // 16
type E2 = Increment<-10>; // -9
type E3 = Increment<0.5>; // never
NOTES:
If you want to use _ToNum
utility type in other scenarios, use this instead:
// Main type
type ToNum<Str extends string> = _RemLeadingZeros<Str> extends infer ValidStr
? ValidStr extends `${infer Start}.${infer End}`
? (
Start extends ""
? End extends ""
? never
: `0.${End}`
: End extends ""
? Start
: Start extends "-"
? `-0.${End}`
: ValidStr
) extends infer InferredStr
? InferredStr extends `${infer Num extends number}`
? Num
: never
: never
: ValidStr extends `${infer Num extends number}`
? Num
: never
: never;
// Helpers
type _RemLeadingZeros<
Str extends string,
COUNT extends any[] = []
> = Str extends `${infer First}${infer Rest}`
? First extends "0"
? Rest extends ""
? First
: _RemLeadingZeros<Rest, [...COUNT, any]>
: First extends "-"
? COUNT["length"] extends 0
? Str
: never
: Str
: never;
// Examples
type X1 = _ToNum<"05">; // 5
type X2 = _ToNum<".5">; // 0.5
type X3 = _ToNum<"5.">; // 5
type X4 = _ToNum<"-.5">; // -0.5
This one has more validations and works well in these examples While the previous _ToNum
will return number
instead of an actual number.
You don't need this type in the increment
type, it will only add more expensive calculation, use the simpler one instead
Upvotes: 0
Reputation: 7642
The following solution has no recursion limit of n > 999
. The only limit is n.toString().length > 1000
; This seems to be a limit of javascript's ability to identify a number. Once you pass that limit, the typescript will show the incremented type as a number
.
type IncrementMap = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
type LastDigitMap = {
10: 0;
11: 1;
12: 2;
13: 3;
14: 4;
15: 5;
16: 6;
17: 7;
18: 8;
};
type LastCharacter<T extends string> = T extends `${infer First}${infer Last}`
? Last extends ''
? First
: LastCharacter<Last>
: T;
export type _Increment<
Number extends string,
Carry extends 0 | 1 = 0,
Result extends string = '',
> = Number extends ''
? Carry extends 0
? Result
: `${Carry}${Result}`
: LastCharacter<Number> extends `${infer LastDigit extends number}`
? IncrementMap[LastDigit] extends infer Incremented extends number
? Number extends `${infer Rest}${LastDigit}`
? Incremented extends keyof LastDigitMap
? _Increment<Rest, 1, `${LastDigitMap[Incremented]}${Result}`>
: `${Rest}${Incremented}${Result}`
: never
: never
: never;
type Increment<T extends number> = _Increment<
`${T}`,
1
> extends `${infer Result extends number}`
? Result
: never;
The logic of the given solution is similar to summing up big numbers as a string. We start with the rightest character and add one.
The addition is done with the IncrementMap
type, where the indexes are the numbers we are trying to increment, and the values are the actual incremented numbers we want to get.
Testing:
type Case1 = Increment<1>; // 2
type Case2 = Increment<9>; // 10
type Case3 = Increment<999>; // 1000
type Case4 = Increment<1899999999999999> // 1900000000000000;
Link to playground
Upvotes: 0
Reputation: 11
@abdurahmanus solution with limit of 1000 recursive calls, but in 1 helper type:
type Inc<N extends number, T extends any[] = []> = T["length"] extends N ? [...T, any]["length"] : Inc<N, [...T, any]>;
Upvotes: 0
Reputation: 101
continuation to @jcalz comment above the Decrement:
type _DecDigit = [9, 0, 1, 2, 3, 4, 5, 6, 7, 8];
type Digit = _DecDigit[number];
type _Dec<T extends string> = T extends "10"
? 9
: T extends `${infer F}${Digit}`
? T extends `${F}${infer L extends Digit}`
? `${L extends 0 ? _Dec<F> : F}${_DecDigit[L]}`
: never
: 1;
export type Decrement<T extends number> = T extends 0
? number
: T extends 10
? 9
: number extends T
? number
: `${T}` extends `${string}${"." | "+" | "-" | "e"}${string}`
? number
: _Dec<`${T}`> extends `${infer N extends number}`
? N
: never;
Upvotes: 0
Reputation: 327614
TypeScript still doesn't natively support mathematical operations on numeric literal types. There is a longstanding open feature request for it at microsoft/TypeScript#26382.
But nowadays you could, if you must, use template literal types to increment numeric literal types by converting the numeric literal into a string literal; performing character-by-character decimal addition on that string literal, and converting the resulting string literal back to a numeric literal.
Frankly this is probably overkill for any reasonable use case, but it's possible and doesn't tax the compiler too much.
In what follows, we will restrict the inputs to numeric literals that represent non-negative whole numbers less than Number.MAX_SAFE_INTEGER
. For any other input (e.g., number
itself, negative numbers, fractions, very large numbers) the output will just be number
. These could mostly be worked around if necessary by careful review of the rules for Number.toString(10)
and for the JavaScript encoding of number
s as double-precision 64-bit binary format IEEE 754, but I'm not going to bother with those here.
This is the implementation:
type _IncDigit = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
type Digit = _IncDigit[number];
type _Inc<T extends string> =
T extends `${infer F}${Digit}` ? T extends `${F}${infer L extends Digit}` ?
`${L extends 9 ? _Inc<F> : F}${_IncDigit[L]}` :
never : 1
type Increment<T extends number> =
number extends T ? number :
`${T}` extends `${string}${"." | "+" | "-" | "e"}${string}` ? number :
_Inc<`${T}`> extends `${infer N extends number}` ? N : never
The _IncDigit
type is a utility tuple which encodes how to increment a single digit without worrying about carry; so _IncDigit[0]
is 1
, and _IncDigit[5]
is 6
, all the way up to _IncDigit[9]
being 0
.
Then _Inc<T>
is the basic increment operation which assumes that T
is a string representation of a valid numeric input. If T
has at least one character it splits it into the last digit L
and the stuff before it F
. L
can be incremented with _IncDigit
, so IncDigit[L]
is the last digit of the result. If L
isn't 9
then we can just prepend F
as-is; otherwise we also have to increment F
, so we recurse to determine _Inc<F>
.
Finally, Increment<T>
takes care of turning strings to numbers and numbers to strings, as well as validating the input.
Let's test it out:
type Inc0 = Increment<0> // 1
type Inc5 = Increment<5> // 6
type Inc9 = Increment<9> // 10
type Inc8675309 = Increment<8675309>; // 8675310
type IncFrac = Increment<2.5> // number
type IncNeg = Increment<-2> // number
type IncTinyFrac = Increment<1.0e-4>; // number
type IncHuge = Increment<9.9e99> // number
type IncNumber = Increment<number>; // number
type Inc3299999999999999 = Increment<3299999999999999>
// type Inc3299999999999999 = 3300000000000000
type CloseToMax = Increment<8999999999999999>
// type CloseToMax = 9000000000000000
type MaxSafeInteger = Increment<9007199254740991>
// type MaxSafeInteger = 9007199254740992
type TooBig = Increment<9007199254740992>
// type TooBig = number
Looks good.
Again, though, I don't know that such an approach is actually warranted by any normal use case. If you're doing this for fun, great. But if you think you find yourself wanting to do this for some production code base somewhere, consider carefully whether to what extent you actually need to re-implement mathematical operations in the type system. It's quite likely that a restricted approach such as hardcoding the results of incrementing all numbers from 0
to 100
or something, will also meet your needs.
Upvotes: 7
Reputation: 327614
This answer is obsolete since recursive conditional types were introduced in TS4.1. And since TS4.8 introduced support for converting string literal types to number literal types. See this answer instead for a solution involving 🤢 base-10 arithmetic on string representations of numbers.
I would just hardcode it like this:
type Increment<N extends number> = [
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,
21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,
38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54, // as far as you need
...number[] // bail out with number
][N]
type Zero = 0
type One = Increment<Zero> // 1
type Two = Increment<One> // 2
type WhoKnows = Increment<12345>; // number
As I said in the other comments, there's currently no great support for this kind of naturally recursive type. I would love it if it were supported, but it's not there. In practice I've found that if something can handle tuples up to length 20 or so it's good enough, but your experience may differ.
Anyway, if anyone does come up with a solution here that isn't hardcoded but also works and performs well for arbitrary numbers (where Increment<123456789>
will evaluate to 123456790
) I'd be interested to see it. Maybe one day in the future it will be part of the language.
Upvotes: 6
Reputation: 61
A slightly different solution. But with the same limitations of 1000 recursive calls
type Arr<N extends number, T extends any[] = []> = T['length'] extends N ? T : Arr<N, [...T, any]>
type Inc<N extends number> = [...Arr<N>, any]['length']
type I20 = Inc<19>
Upvotes: 5
Reputation: 294
This solution is not hard coded but isn't useful due to TypeScript's recursion limits. When I've tested it, it does not handle Number2Nat
greater than 45.
type SomeNat = [...unknown[]];
type Zero = [];
type Succ<N extends SomeNat> = [...N, unknown];
type Nat2Number<N extends SomeNat> = N["length"];
type Dec<N extends SomeNat> = N extends [unknown, ...infer T] ? T : never;
type Add<N extends SomeNat, M extends SomeNat> = [...N, ...M];
type Sub<N extends SomeNat, M extends SomeNat> = M extends Zero ? N : Sub<Dec<N>, Dec<M>>;
type Number2Nat<I extends number, N extends SomeNat = Zero> = I extends Nat2Number<N> ? N : Number2Nat<I, Succ<N>>;
Upvotes: 2