前言
上一篇我们只实现了简单的数值比较,并留下了一个问题;实现大数字的比较。
前置知识
ts类型实例化过程太深,会导致报错"Type instantiation is excessively deep and possibly infinite"
type MarkArr<N extends number, R extends unknown[] = []> = R['length'] extends N
? R
: MarkArr<N, [0, ...R]>;
type Result = MarkArr<999>; // 没有报错
type Result2 = MarkArr<1000>; // error Type instantiation is excessively deep and possibly infinite
思路
两个数字比较,有如下几种情况(上一篇已经考虑负数的问题了,这里就不再赘述):
1. 只有一位数字,可以直接比较
2. 有多位数字,但是长度不想等,可以直接判断长度来比较大小
3. 有多位数字,且长度相等,可以都从第一位开始判断,大于则是A大于B
多位数字的对比,可以先把数字转换成数组,思路还是字符串infer
type Number2Array<
T extends string | number,
A extends string[] = [],
> = `${T}` extends `${infer S}${infer O}` ? Number2Array<O, [...A, S]> : A;
有了数字的数组之后,就可以开始进行对比了。可以用一个指针每次对比,如果相同,则指针+1,不相同测返回,这样就可以对整个数组进行对比了。
type ComparisonNumberArr<
S extends string[],
E extends string[],
P extends number = 0,
> =
// 长度是否相同
S['length'] extends E['length']
// 长度相同,则开始从P(默认是第一位0)位开始计算
? S[P] extends E[P]
// 如果相同,则P指针加1,继续计算
? ComparisonNumberArr<S, E, Plus<P, 1> & number>
// 计算
: CompareNumber<S[P], E[P]>
// 长度不相同,则对比长度就可以知道大小了
: CompareNumber<S['length'], E['length']>;
观察上面的代码,都是10以内的数字比较。我们可以生成一个table,预计算(Early Table Generation)数字对比的结果,而不是运行时计算。
// 0-9
type num = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
type NumberMap = {
[Key in `${num[number]}${num[number]}`]: Key extends `${infer A}${infer B}`
? A extends B
? Comparison.Equal
: CompareNumber<String2Number<A>, String2Number<B>>
: never;
};
则ComparisonNumberArr
可以改为:
type ComparisonNumberArr<
S extends string[],
E extends string[],
P extends number = 0,
> = S['length'] extends E['length']
? S[P] extends E[P]
? ComparisonNumberArr<S, E, Plus<P, 1> & number>
// 类型收窄
: `${S[P]}${E[P]}` extends keyof NumberMap
? NumberMap[`${S[P]}${E[P]}`]
: never
: CompareNumber<S['length'], E['length']>;
完整的代码和测试用例实现如下
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
? true
: false;
type Expect<T extends true> = T;
enum Comparison {
Greater,
Equal,
Lower,
}
type num = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
type MarkArr<N extends number, R extends unknown[] = []> = R['length'] extends N ? R : MarkArr<N, [0, ...R]>;
type Plus<A extends number, B extends number> = [...MarkArr<A>, ...MarkArr<B>]['length'];
type String2Number<S extends string, T extends any[] = []> = `${S}` extends `${T['length']}`
? T['length']
: String2Number<S, [0, ...T]>;
// 对比两个数字大小
type CompareNumber<A extends number, B extends number> = keyof MarkArr<A> extends keyof MarkArr<B>
? Comparison.Lower
: Comparison.Greater;
// 生成对照表
type NumberMap = {
[Key in `${num[number]}${num[number]}`]: Key extends `${infer A}${infer B}`
? CompareNumber<String2Number<A>, String2Number<B>>
: never;
};
// 对比长度
type ComparisonNumberArr<
S extends string[],
E extends string[],
P extends number = 0,
> =
// 长度是否相同
S['length'] extends E['length']
// 长度相同,则开始从P(默认是第一位0)位开始计算
? S[P] extends E[P]
// 如果相同,则P指针加1,继续计算
? ComparisonNumberArr<S, E, Plus<P, 1> & number>
// 类型收窄
: `${S[P]}${E[P]}` extends keyof NumberMap
? NumberMap[`${S[P]}${E[P]}`]
: never
// 长度不相同,则对比长度就可以知道大小了
: CompareNumber<S['length'], E['length']>;
// 数字转为字符串的数组
type TowNumberArray<
T extends string | number,
A extends string[] = [],
> = `${T}` extends `${infer S}${infer O}` ? TowNumberArray<O, [...A, S]> : A;
type Comparator<M extends number, N extends number> =
// 相等
M extends N
? Comparison.Equal
// 都是负数,则反转计算的结果
: `${M}${N}` extends `-${infer M1}-${infer N1}`
? ComparisonNumberArr<TowNumberArray<M1>, TowNumberArray<N1>> extends Comparison.Lower
? Comparison.Greater
: Comparison.Lower
// 第一个为负数
: `${M}${N}` extends `-${infer M1}${infer N1}`
? Comparison.Lower
// 第二个为负数
: `${M}${N}` extends `${infer M1}-${infer N1}`
? Comparison.Greater
// 都是正数
: `${M}${N}` extends `${infer M2}${infer N2}`
? ComparisonNumberArr<TowNumberArray<M>, TowNumberArray<N>>
: never;
type cases = [
Expect<Equal<Comparator<5, 5>, Comparison.Equal>>,
Expect<Equal<Comparator<5, 6>, Comparison.Lower>>,
Expect<Equal<Comparator<5, 8>, Comparison.Lower>>,
Expect<Equal<Comparator<5, 0>, Comparison.Greater>>,
Expect<Equal<Comparator<-5, 0>, Comparison.Lower>>,
Expect<Equal<Comparator<0, 0>, Comparison.Equal>>,
Expect<Equal<Comparator<0, -5>, Comparison.Greater>>,
Expect<Equal<Comparator<5, -3>, Comparison.Greater>>,
Expect<Equal<Comparator<5, -7>, Comparison.Greater>>,
Expect<Equal<Comparator<-5, -7>, Comparison.Greater>>,
Expect<Equal<Comparator<-5, -3>, Comparison.Lower>>,
Expect<Equal<Comparator<-25, -30>, Comparison.Greater>>,
Expect<Equal<Comparator<15, -23>, Comparison.Greater>>,
Expect<Equal<Comparator<40, 37>, Comparison.Greater>>,
Expect<Equal<Comparator<-36, 36>, Comparison.Lower>>,
Expect<Equal<Comparator<27, 27>, Comparison.Equal>>,
Expect<Equal<Comparator<-38, -38>, Comparison.Equal>>,
Expect<Equal<Comparator<1, 100>, Comparison.Lower>>,
Expect<Equal<Comparator<100, 1>, Comparison.Greater>>,
Expect<Equal<Comparator<-100, 1>, Comparison.Lower>>,
Expect<Equal<Comparator<1, -100>, Comparison.Greater>>,
Expect<Equal<Comparator<-100, -1>, Comparison.Lower>>,
Expect<Equal<Comparator<-1, -100>, Comparison.Greater>>,
Expect<Equal<Comparator<9007199254740992, 9007199254740992>, Comparison.Equal>>,
Expect<Equal<Comparator<-9007199254740992, -9007199254740992>, Comparison.Equal>>,
Expect<Equal<Comparator<9007199254740991, 9007199254740992>, Comparison.Lower>>,
Expect<Equal<Comparator<9007199254740992, 9007199254740991>, Comparison.Greater>>,
Expect<Equal<Comparator<-9007199254740992, -9007199254740991>, Comparison.Lower>>,
Expect<Equal<Comparator<-9007199254740991, -9007199254740992>, Comparison.Greater>>,
];