对此的简短回答是,目前在 TypeScript 中不可能对数值执行任意数学计算文字类型 https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types。有一个(相当长期的)开放功能请求:微软/TypeScript#26382 https://github.com/microsoft/TypeScript/issues/26382要求这个。您可能想去那里给它一个????,并且由于其状态是“等待更多反馈”,如果您认为它引人注目,您可能想在那里留下评论,详细说明您的用例。但这可能不会产生太大的影响,所以现在最好假设类型级数学不会很快发生。
您可以通过操作说服编译器执行某种数学运算元组类型 https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types like [any, any, any]
。元组类型有length
属性是数字文字,您可以使用可变元组类型 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-0.html#variadic-tuple-types and 递归条件类型 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html#recursive-conditional-types更改元组长度。
这意味着您将仅限于对非负整数进行运算(您不能拥有长度为3.14159
or -2
),并且由于类型递归的限制约为 ~25 级深度 1000 级深度(UPDATE:TS4.5 已将其增加到约 1000 个级别尾递归条件类型 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-5.html#tail-recursion-elimination-on-conditional-types所以现在好一点了,但是当涉及到数字时它仍然很小),很难让事情与大数字一起工作。直观的实现往往只适用于小于 1000 左右的数字。有一些不太直观的实现可以处理数千或数万的数字(请参阅这条评论 https://github.com/microsoft/TypeScript/issues/26223#issuecomment-674514787在相关的 GitHub 问题上),但即使这些也涉及实际构建这些长度的元组,因此可能会使编译器陷入困境。即使你非常聪明,你也可能会写出一些复杂而脆弱的东西。边缘情况是到处.
您的原始代码示例是将小数除以二,这可以使用递归来完成,如下所示:
type DivideByTwo<N extends number, T extends 0[] = []> = N extends any ?
0 extends [...T, ...T, 0][N] ? T['length'] : DivideByTwo<N, [0, ...T]> : never;
type X = DivideByTwo<6 | 10 | 30>; // type X = 3 | 5 | 15
这是通过取一个数字来实现的N
和一个元组T
它从空开始([]
)。如果有两份T
连接在一起后跟单个元素,索引处有一个元素N
, then T
至少有两倍大N
,我们只返回长度T
。否则,T
太小了,所以我们向其中添加一个元素,然后重试。这会将小数字切成两半:如果N
is 10
, then T
变成[]
, then [0]
, then [0,0]
, then [0,0,0]
, then [0,0,0,0]
, then [0,0,0,0,0]
满足原始检查,所以你得到5
.
The N extends any...
部分进行操作分配性的 https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types超过工会N
,所以输入的并集成为输出的并集......所以DivideByTwo<10 | 20>
is 5 | 10
(按某种顺序)。
但后来你改变了你的例子,将大得多的数字除以十六。为了开始这样做,我必须开始使用重复加倍元组的技巧(这意味着递归深度限制最终看起来像是数字对数的限制,而不是数字本身的限制)。尝试对除数(分子,大数)和除数(分母,这里的 16)都这样做对我来说根本不值得尝试。这是一个硬编码的东西,似乎适用于除以十六:
type Quadruple<T extends any[]> = [...T, ...T, ...T, ...T]
type Explode<N extends number, R extends never[][]> =
Quadruple<R[0]>[N] extends never ? R : Explode<N, [[...R[0], ...R[0]], ...R]>;
type BinaryBuilder<N extends number, R extends never[][], B extends never[]> =
Quadruple<[...B, never]>[N] extends never ? B :
Quadruple<[...R[0], ...B]>[N] extends never ? BinaryBuilder<N, R extends [R[0], ...infer U] ? U extends never[][] ? U : never : never, B> :
BinaryBuilder<N, R extends [R[0], ...infer U] ? U extends never[][] ? U : never : never, [...R[0], ...B]>;
type CutTupleInFour<N extends number> = number extends N ? any[] : N extends number ?
Explode<N, [[never]]> extends infer U ? U extends never[][]
? BinaryBuilder<N, U, []> : never : never : never;
type DivideByFour<N extends number> = CutTupleInFour<N>['length']
type DivideBySixteen<N extends number> = DivideByFour<DivideByFour<N>>
type Foo = 768 | 1024 | 1280;
type Bar = DivideBySixteen<Foo>
// type Bar = 64 | 48 | 80
你喜欢?是的,我也一样。即使详细解释它是如何工作的,我也无能为力。我会说它通过除以四两次来工作(当我直接尝试十六次时,编译器陷入困境并且无法在合理的时间内完成),并且它通过构建重复加倍长度的元组来除以四,停止当它有一个太大时,然后将它们连接在一起以形成正确长度的一个。
草图:假设我们应该划分80
除以 16。编译器首先除80
4. 它构建长度元组32
, 16
, 8
, 4
, 2
, and 1
。然后它连接长度16
and 4
得到长度之一20
。现在又分了20
4,通过构建长度元组8
, 4
, 2
, and 1
。然后它连接长度4
and 1
得到长度之一5
。结果是5
.
但是恶心,丑陋,可怕。在生产中你不想做任何事情。
如果我是你,我会重新检查为什么你希望类型系统为你做这件事而不是你自己做。如果您仍然有充分的理由,那么在 microsoft/TypeScript#26382 上进行游说可能比使用元组跳过障碍更有效。
Playground 代码链接 https://www.typescriptlang.org/play?#code/HYQwtgpgzgDiDGEAEBhATiAXgTwEIFcAXASWEPwEtCKA3ZAbwCgklDsZkARWigEwlzYAKgHcA9gB4AckggAPQhGC8oSYPjAAjCGgA0SIbIVKVSAAwBtALpIAvEmsA+O0hnzFy1SGDYkAfmYWcyMPUwsAOkihfUjw6PMrCykbPwMLAHIAGyUAc0IAC3SbAC4kbho+AWFxaX0LMxioq2dS4Ag6NABuRkC2DiQADRdyysFRSQA2JAAfJABGMxmkAGYzR06kAHpN1nZkIftlpYBWJbnjnoBfHtBIWARkAAkxNDQKTUzsUihvCABBZRSMQkMiUah0JBMQLbJD5QiEGBQYrbHJUfL4TTheBiMCbMAUeBoMRQMQAM0ImyEewAyoSKDAKRQoFB8NBNgAmCbs9nLADETJZEGxYEgZAAtBMAOwAFmOc2lkoAHJKeiw+sgAIr4EC8ND4GDZCSGdwmLw+JwuCJRRpxG3xWJCKyq3b9ACicgNYn40hCprUGm0eiQACVfZ41O0dNYLbZAiwtTq9QaIBJg-VmkkbCbw20Ov4Q0hSu7Pd6pHUreE02YrDaq1Ya0hYsHmt1enskLgKKA0HhKJl+GgfdnTOotDp9KHh6pc1HEg3cGGR5G0DG40gE7r9YaK7h9DO0Bnkovp8v8wvimuN0nt030zbcIes8Yc6fUp3u72KP2dLUC1OHHWNpdqSOhIAAqik4HHhGHTRpBYGFjBoGtMu+i4C0a7viAPYEF+A6-pOz5hIBjaRMBoEQfmCH-vucFUYh+4MahDi3tW94ts66qoEQQhbhApAAGJiPgg5uER04Bjozj2KOgbQTIqTeNg1iIWJoQSWOaD+GuxaZF6KZlg4Fj7vWzj-uRWkIak1HiUhK5zmub5dthn7fqJ+hgXUzRMXmKG+XZrZqu2Iz8IIQkiUOtmyVJLgoDxfGCcJomOBk2TAHkhROm2-QhVU1IUMYSiRep-qadJZQ8KF2DhYOuVhUl0iOI4PTZcgQliC4koTIqZxmOy0pnOyipmN0QX9Lg2HDJVeUFYoRXtc1LAwlxE1afYEwDbM0o9bMw1XD0QA