How can I produce an incremented version of a numeric literal type in TypeScript?
Asked Answered
M

7

9

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,
Maddalena answered 17/1, 2019 at 19:59 Comment(10)
A simple function would be easier if you really want to wrap the increment operation. Why are you trying to use templates for this purpose?Salmagundi
Same answer as the other question... not officially. You might be able to make something work for low numbers, but a hardcoded list of length 100 or so (e.g., [1,2,3,4,5,6,7...]), will probably perform better and cause you fewer headaches, unfortunately.Treadwell
@Treadwell your comments save me tons of time of further failed experiments. Thank you!Maddalena
@IanMacDonald Sorry I can't get the solution with a simple function, can you show it? Also I am not using any templates, am I?Maddalena
function increment(input: number): number { return input+1 }?Salmagundi
@IanMacDonald But I need a type conversion, not a value conversion. Why? Even your function does not provide an incrementation of type, because both input and output types are just number in contrast to exact number literals (e.g. 1,2,55 etc.)Maddalena
I'm still not sure why you're trying to use a literal number as a type.Salmagundi
@IanMacDonald Probably the simplest example is when you need to get a tuple item at exact position (index). But you also don't know that position, you have to calculate it based on another tuple item's index. Hence I needed the increment type in the first place.Maddalena
You still appear to be talking in values, not types.Salmagundi
@IanMacDonald But I want IDE tooling to work as well.Maddalena
T
6

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.

Treadwell answered 17/1, 2019 at 20:21 Comment(1)
Thank you! I actually did it almost the same but more silly way (I mentioned IncrementMap in the question. I forgot that I don't need to specify keys 0,1,2 when I can just use a tuple). BTW that bail out part is great!! Just noticed it!Maddalena
T
6

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 numbers 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.

Playground link to code

Treadwell answered 12/5, 2023 at 3:57 Comment(0)
M
5

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>
Might answered 31/8, 2022 at 11:14 Comment(2)
for others searching Decrement: type Decrement<N extends number, T extends number[] = []> = Arr<N> extends [any, ...infer U] ? U['length'] : neverEdveh
Can be further simplified to type Decrement<N extends number> = Arr<N> extends [any, ...infer U] ? U['length'] : never since the T arg is never being actually used.Hygrophilous
S
2

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>>;
Sylvan answered 20/5, 2021 at 17:28 Comment(1)
This works up to 1000 from Typescript 4.5 due to tail-recursion elimination on conditional types.Withoutdoors
R
0

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

Rutty answered 13/4, 2023 at 21:39 Comment(0)
E
0

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;
Electric answered 28/8, 2023 at 8:20 Comment(0)
C
0

@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]>;
Calling answered 21/12, 2023 at 12:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.