比较任意数字大于
来自:type-challenges (opens in a new tab)
背景是处理数字格式化函数的类型推导,需要单位推导,所以需要比较传入的数字和 base 的大小,给出正确的单位作为类型(见最后)
高级体操,一开始咱也看不懂,issue 中也有翻译成代码的版本(type zen)便于理解,最后有解析
问题来了,实现的是
>
,那么>=
怎么做呢?难道要把Equal
那块代码抄过来吗。。。yes
最终答案:看最后
看一下复杂的第二版
GreaterThanEqual
1000 >= 1000
true!
type GE<T extends number, U extends number> = Equals<T, U> extends true
? true
: GreaterThan<T, U>;
GreaterThan
// compares the first digits of T and U. If they are the same - compare rest of T and rest of U
// prettier-ignore
type GreaterThanSameDigitCount<
T extends number | string,
U extends number | string
> = `${T}` extends `${infer TF}${infer TR}`
? `${U}` extends `${infer UF}${infer UR}`
? TF extends UF
? GreaterThanSameDigitCount<TR, UR>
: "0123456789" extends `${string}${TF}${string}${UF}${string}`
? false
: true
: true
: false;
type DigitsToArr<S extends string> = S extends `${string}${infer R}`
? [0, ...DigitsToArr<R>]
: [];
type ArrLenCompare<
T extends any[],
U extends any[]
> = "0123456789" extends `${string}${T["length"]}${string}${U["length"]}${string}`
? -1
: "0123456789" extends `${string}${U["length"]}${string}${T["length"]}${string}`
? 1
: 0;
type GreaterThan<T extends number, U extends number> = ArrLenCompare<
DigitsToArr<`${T}`>,
DigitsToArr<`${U}`>
> extends 0
? GreaterThanSameDigitCount<T, U>
: ArrLenCompare<DigitsToArr<`${T}`>, DigitsToArr<`${U}`>> extends 1
? true
: false;
使用
格式化数字,返回 [decimal, unit]
const base = 10000;
/**
* getW(11000) -> ['1.1', '万']
* getW(1100) -> ['1100', '']
* getW(11000, ['', 'w']) -> ['1.1', 'w']
*/
export function getW<T extends number, S extends Readonly<[string, string]>>(
initNum: T,
suffix: S
): GreaterThan<T, typeof base> extends true
? [string, (typeof suffix)[1]]
: [string, (typeof suffix)[0]] {
const decimal = ...; // 伪实现 不重要
const unit = initNum < base ? suffix[0] || "" : suffix[1] || "万";
return [`${decimal}`, unit];
}
const re = getW(110000, ["", "万"] as const);
// re -> [string, "万"]
浅析
type-zen 这个工具有点东西,翻译之后 (opens in a new tab)还是比较能看懂的
GreaterThan
比较两个数的大小
首先进入的是 ArrLenCompare
,但他接受的是一个数组,通过 DigitsToArr
构造
DigitsToArr
代码如下:这个不需要翻译,很好看明白
将 "1345" 这样的数字字符串转换成 [0, '1', '3', '4', '5']
这样的数组
type DigitsToArr<S extends string> = S extends `${string}${infer R}`
? [0, ...DigitsToArr<R>]
: [];
然后回到 ArrLenCompare
,看看是如何比较的
ArrLenCompare
这里先说结论,ArrLenCompare
比较的是两个数组的长度,也就是两个数的位数,T 数组长度大于 U 数组则返回 1,反之则 -1,相等或者位数超过 9 则返回 0
通过了一个比较骚/牛逼的方法,就是通过字符串的比较的推导,将两个数组的长度 TL 和 UL,分别按「先后」,「后先」的顺序组合成字符串,比如 TL = 3,UL = 4 → "34"
"43"
,最后再去和一个生序字符串 "123456789"
进行判断,得出哪个数组长度多,那么这个对应的数的位数就多,数就大!
type ArrLenCompare<T: any[], U: any[]> = ^{
type LC = '0123456789';
type RC1 = `${string}${T['length']}${string}${U['length']}${string}`
type RC2 = `${string}${U['length']}${string}${T['length']}${string}`
if (LC == RC1) {
return -1
} else if (LC == RC2) {
return 1
}
return 0
}
如果两个数位数一样,结果就是 0,会继续交给 GreaterThanSameDigitCount
进行比较
问题来了,这里的位数最大就到 9,其实也就限制了最大的数字范围
GreaterThanSameDigitCount
首先解决一下下面这行干了什么,其实得到的 TF 就是第一个(first),TR 是剩下的(rest)
`${T}` extends `${infer TF}${infer TR}`
? `${U}` extends `${infer UF}${infer UR}`
于是整个类型就比较清晰了:
- 在位数相同的前提下(不相同,仅从高位开始比较,则会又问题)
- 取各自最高位,判断是否相同
- 相同则继续递归比较剩下的后 n 位
- 不相同则判断哪个大,还是通过两个前后顺序摆放后的比较
- 最后递归结束条件是 T 和 U 都是
""
,此时通过第一个 infer 之后 TF 和 TR 其实无法推出,所以直接到了 false 分支,结束
type GreaterThanSameDigitCount<T: number | string, U: number | string> = ^{
if (`${T}` == `${infer TF}${infer TR}`) {
if (`${U}` == `${infer UF}${infer UR}`) {
if (TF == UF) {
return GreaterThanSameDigitCount<TR, UR>
} else {
type LC = '0123456789';
type RC = `${string}${TF}${string}${UF}${string}`
return LC == RC ? false : true
}
} else {
return true
}
}
return false
}
最后
还是挺牛逼的。。思路清奇!小于、小于等于就直接复用 GreaterThan 即可
琢磨了很久想突破位数 9 的限制,支持 string 的数字,还挺难的。。
试了下第一个回答是可以的也是非常简单的思路,牛逼。
- 直接通过
&
判断字符串是否相等了,不想等就是never
- Res 作为第一位大小比较的变量做递归
type GreaterThan<
T extends number | string,
U extends number | string,
Res = false
> = `${T}` extends `${infer TF}${infer TR}`
? `${U}` extends `${infer UF}${infer UR}`
? [Res, TF & UF] extends [false, never] // Res == false and TF != UF
? GreaterThan<
TR,
UR,
"0123456789" extends `${string}${TF}${string}${UF}${string}`
? false
: true
>
: GreaterThan<TR, UR, Res>
: true
: U extends ""
? Res
: false;