如何在 TypeScript 中生成数字文字类型的增量版本?

Nur*_*yev 9 increment typescript typescript-typings

是否有可能从数字类型T得到Y值为T+1的数字类型。

type one = 1

type Increment<T extends number> = ???

type two = Increment<one> // 2
Run Code Online (Sandbox Code Playgroud)

PS 目前,我有增量值的硬编码接口,但问题是硬编码的,因此受到限制:

export type IncrementMap = {
    0: 1,
    1: 2,
    2: 3,
Run Code Online (Sandbox Code Playgroud)

jca*_*alz 6

由于TS4.1 中引入了递归条件类型,因此该答案已过时。并且自从 TS4.8 引入了对将字符串文字类型转换为数字文字类型的支持。请参阅此答案,了解涉及数字字符串表示形式的基数 10 算术的解决方案。


我会像这样硬编码它:

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
Run Code Online (Sandbox Code Playgroud)

正如我在其他评论中所说,目前对这种自然递归类型没有很好的支持。如果它得到支持我会很高兴,但它不存在。在实践中,我发现如果某个东西可以处理长度达到 20 左右的元组就足够了,但您的体验可能会有所不同。

无论如何,如果有人确实在这里提出了一个解决方案,该解决方案不是硬编码的,但对于任意数字也能很好地工作和执行(其中Increment<123456789>将评估为123456790),我有兴趣看到它。也许将来的某一天它会成为语言的一部分。


jca*_*alz 5

TypeScript 本身仍然不支持数字文字类型的数学运算。microsoft/TypeScript#26382上有一个长期开放的功能请求。

但现在,如果必须的话,您可以使用模板文字类型通过将数字文字转换为字符串文字来增加数字文字类型;对该字符串文字执行逐个字符的十进制加法,并将结果字符串文字转换回数字文字。

坦率地说,这对于任何合理的用例来说可能都太过分了,但这是可能的,并且不会给编译器带来太大的负担。

接下来,我们将输入限制为表示小于 的非负整数的数字文字Number.MAX_SAFE_INTEGER。对于任何其他输入(例如,number本身、负数、分数、非常大的数字),输出将只是number。如有必要,可以通过仔细检查 s 作为双精度 64 位二进制格式 IEEE 754 的 JavaScript 编码规则来解决这些问题Number.toString(10)number不会在这里打扰这些。


这是实现:

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
Run Code Online (Sandbox Code Playgroud)

_IncDigit类型是一个实用元组,它编码如何递增单个数字而不用担心进位;如此,一直_IncDigit[0]如此,一直到存在。1_IncDigit[5]6_IncDigit[9]0

然后_Inc<T>是基本的增量操作,它假设T是有效数字输入的字符串表示形式。如果T至少有一个字符,它将把它分成最后一个数字L和它之前的内容FL可以随 递增_IncDigitIncDigit[L]结果的最后一位数字也是如此。如果L不是9,那么我们可以按F原样添加;否则我们还必须增加F,因此我们递归地确定_Inc<F>

最后,Increment<T>负责将字符串转换为数字,将数字转换为字符串,以及验证输入。

让我们测试一下:

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
Run Code Online (Sandbox Code Playgroud)

看起来不错。


但同样,我不知道任何正常用例实际上都可以保证这种方法。如果你这样做是为了好玩,那就太好了。但是,如果您认为自己想要在某个地方对某些生产代码库执行此操作,请仔细考虑您是否实际上需要在类型系统中重新实现数学运算到什么程度。0一种受限制的方法(例如将所有数字从增加到或某事的结果硬编码100)很可能也能满足您的需求。

Playground 代码链接