Skip to content

Instantly share code, notes, and snippets.

@jet2jet
Last active August 6, 2022 12:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jet2jet/519216607115127889b2bf486edf21e1 to your computer and use it in GitHub Desktop.
Save jet2jet/519216607115127889b2bf486edf21e1 to your computer and use it in GitHub Desktop.
Addition and subtraction for numeric types in TypeScript 4.8 (https://www.pg-fl.jp/program/tips/ts_typecalc2.htm (japanese))
// for license: see https://gist.github.com/jet2jet/93fab0cbd08e89bf47f81835a2dfe46c
// *** Must be used with TS 4.8 or later ***
type NumericChars = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
interface DigitToTuple {
'0': [];
'1': [''];
'2': ['', ''];
'3': ['', '', ''];
'4': ['', '', '', ''];
'5': ['', '', '', '', ''];
'6': ['', '', '', '', '', ''];
'7': ['', '', '', '', '', '', ''];
'8': ['', '', '', '', '', '', '', ''];
'9': ['', '', '', '', '', '', '', '', ''];
'10': ['', '', '', '', '', '', '', '', '', ''];
'11': ['', '', '', '', '', '', '', '', '', '', ''];
'12': ['', '', '', '', '', '', '', '', '', '', '', ''];
'13': ['', '', '', '', '', '', '', '', '', '', '', '', ''];
'14': ['', '', '', '', '', '', '', '', '', '', '', '', '', ''];
'15': ['', '', '', '', '', '', '', '', '', '', '', '', '', '', ''];
'16': ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''];
'17': ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''];
'18': ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''];
'19': ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''];
}
type IntTupleToIntString<T extends ['-' | '', ...NumericChars[]] | NumericChars[]> =
T extends [infer D extends NumericChars | '-' | '', ...infer R extends NumericChars[]]
? `${D}${IntTupleToIntString<R>}`
: T extends []
? ''
: never;
type ParseIntStringToIntTupleInner<T extends string> =
string extends T
? never
: T extends `${infer P extends NumericChars}${infer Q extends string}`
? Q extends ''
? [P]
: [P, ...ParseIntStringToIntTupleInner<Q>]
: never;
type ParseIntStringToIntTuple<T extends string> =
string extends T
? never
: T extends `-${infer R}`
? ['-', ...ParseIntStringToIntTupleInner<R>]
: ['', ...ParseIntStringToIntTupleInner<T>];
type AddXTemp<A extends NumericChars | '10', B extends NumericChars> =
[...DigitToTuple[A], ...DigitToTuple[B]]['length'] extends infer N extends number
? `${N}`
: never;
type Carried<D extends NumericChars, C extends '1' | '0'> =
C extends '1'
? [...DigitToTuple[D], '']['length'] extends infer N extends NumericChars | '10'
? `${N}`
: never
: D;
type AddInner<A extends NumericChars[], B extends NumericChars[], C extends '1' | '0' = '0'> =
A extends []
? C extends '1'
? AddInner<B, ['1']>
: B
: B extends []
? C extends '1'
? AddInner<A, ['1']>
: A
: A extends [...infer R extends NumericChars[], infer DA extends NumericChars]
? B extends [...infer S extends NumericChars[], infer DB extends NumericChars]
? AddXTemp<Carried<DA, C>, DB> extends infer G
? [
...AddInner<
R,
S,
G extends `1${NumericChars}`
? '1'
: '0'
>, G extends `1${infer DG extends NumericChars}` ? DG : G
]
: never
: never
: never;
type SubXTemp<A extends NumericChars, B extends NumericChars, C extends '1' | '0'> =
C extends '1'
? DigitToTuple[A] extends [...DigitToTuple[B], '', ...infer R]
? ['0', `${R['length']}`]
: DigitToTuple[`1${A}`] extends [...DigitToTuple[B], '', ...infer R]
? ['1', `${R['length']}`]
: never
: DigitToTuple[A] extends [...DigitToTuple[B], ...infer R]
? ['0', `${R['length']}`]
: DigitToTuple[`1${A}`] extends [...DigitToTuple[B], ...infer R]
? ['1', `${R['length']}`]
: never;
type NumCharTupleToEmptyStrTuple<T extends NumericChars[]> =
T extends [NumericChars, ...infer R extends NumericChars[]]
? ['', ...NumCharTupleToEmptyStrTuple<R>]
: [];
// Must be A >= B
type SubInner2<A extends NumericChars[], B extends NumericChars[], C extends '1' | '0' = '0', HeadZero extends '0'[] = []> =
A extends []
? B extends []
? ['']
: never
: B extends []
? C extends '1'
? [...SubInner2<A, ['1'], '0', HeadZero>]
: ['', ...A, ...HeadZero]
: A extends [...infer R extends NumericChars[], infer DA extends NumericChars]
? B extends [...infer S extends NumericChars[], infer DB extends NumericChars]
? SubXTemp<DA, DB, C> extends [infer F extends '1' | '0', infer D extends NumericChars]
? D extends '0'
? [...SubInner2<R, S, F, [D, ...HeadZero]>]
: [...SubInner2<R, S, F>, D, ...HeadZero]
: never
: never
: never;
type CompareNumTuple<A extends NumericChars[], B extends NumericChars[]> =
A extends []
? B extends []
? 0
: -1
: B extends []
? 1
: NumCharTupleToEmptyStrTuple<A> extends [...NumCharTupleToEmptyStrTuple<B>, '', ...''[]]
? 1
: NumCharTupleToEmptyStrTuple<B> extends [...NumCharTupleToEmptyStrTuple<A>, '', ...''[]]
? -1
: A extends [infer DA extends NumericChars, ...infer RA extends NumericChars[]]
? B extends [infer DB extends NumericChars, ...infer RB extends NumericChars[]]
? DigitToTuple[DA] extends [...DigitToTuple[DB], '', ...''[]]
? 1
: DigitToTuple[DB] extends [...DigitToTuple[DA], '', ...''[]]
? -1
: CompareNumTuple<RA, RB>
: never
: never;
type SubInner<A extends NumericChars[], B extends NumericChars[]> =
CompareNumTuple<A, B> extends infer C extends 1 | 0 | -1
? C extends 1
? SubInner2<A, B>
: C extends 0
? ['', '0']
: SubInner2<B, A> extends ['', ...infer R extends NumericChars[]]
? ['-', ...R]
: never
: never;
type AddImpl<A extends ['-' | '', ...NumericChars[]], B extends ['-' | '', ...NumericChars[]]> =
A extends [infer FA extends '-' | '', ...infer IA extends NumericChars[]]
? B extends [infer FB extends '-' | '', ...infer IB extends NumericChars[]]
? `${FA}${FB}` extends '--'
? ['-', ...AddInner<IA, IB>]
: `${FA}${FB}` extends '-'
? FA extends '-'
? SubInner<IB, IA>
: SubInner<IA, IB>
: ['', ...AddInner<IA, IB>]
: never
: never;
type AddIntString<A extends string, B extends string> =
IntTupleToIntString<AddImpl<ParseIntStringToIntTuple<A>, ParseIntStringToIntTuple<B>>>;
type ToggleFlag<A extends ['-' | '', ...NumericChars[]]> =
A extends ['-', ...infer R extends NumericChars[]]
? ['', ...R]
: A extends ['', ...infer R extends NumericChars[]]
? ['-', ...R]
: never;
type SubIntString<A extends string, B extends string> =
IntTupleToIntString<
AddImpl<
ParseIntStringToIntTuple<A>,
ToggleFlag<ParseIntStringToIntTuple<B>>
>
>;
type Add<A extends number, B extends number> =
AddIntString<`${A}`, `${B}`> extends `${infer N extends number}`
? N
: never;
type Sub<A extends number, B extends number> =
SubIntString<`${A}`, `${B}`> extends `${infer N extends number}`
? N
: never;
/** Check whether T is either a numeric literal or 'numeric string' literal */
type CheckIsNumeric<T extends string | number | bigint> =
`${T}` extends `-${infer U}`
? U extends `-${any}` ? false : CheckIsNumeric<U>
: `${T}` extends `${NumericChars}${infer U}`
? U extends '' ? true : CheckIsNumeric<U>
: false;
// idea is from microsoft/TypeScript#26362
// (to support enum types)
type CheckIsNumericLiteralType<T extends number> =
true extends ({ [key: number]: true } & { [P in T]: false })[number] ? true : false
const enum Foo {
Bar = 1,
Baz = 2
}
function add<A extends number, B extends number>(
a: (true extends CheckIsNumericLiteralType<A> & CheckIsNumeric<A> ? A : never),
b: (true extends CheckIsNumericLiteralType<B> & CheckIsNumeric<B> ? B : never)
): Add<A, B> extends infer R extends number
? R
: never;
function add(a: number, b: number): number;
function add(a: number, b: number): number { return a + b; }
// Samples
type Sample1 = Add<123, 456>; // 579
type Sample2 = Add<-10, 30>; // 20
type Sample3 = Add<1000, -1000>; // 0
type Sample4 = Sub<700, 400>; // 300
type Sample5 = Sub<12, 25> // -13;
type Sample6 = Add<10 | 20, 5 | 15>; // 15 | 25 | 35
const sample7 = add(10, 20); // type of sample7 is 30
const sample8 = add(sample7, 30); // type of sample8 is 60
const sample9 = add(Foo.Bar, Foo.Baz); // type of sample9 is 3
const sample10 = add([1].length, 100); // type of sample10 is number (type of [1].length is not a numeric literal type)
const sample11 = add(([1] as const).length, 100); // type of sample11 is 101 (type of ([1] as const).length is 1)
const sample12 = add(10, -20); // type of sample12 is -10
const sample13 = add(1.1, 2); // UNSUPPORTED CASE: type of sample13 is number
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment