Skip to content

Instantly share code, notes, and snippets.

@ryangoree
Created July 7, 2025 17:40
Show Gist options
  • Save ryangoree/720fb32e57322914cfe74622974b9344 to your computer and use it in GitHub Desktop.
Save ryangoree/720fb32e57322914cfe74622974b9344 to your computer and use it in GitHub Desktop.
🚧 WIP 🚧 - Parse a number-like value in TypeScript types.
//--- Tuple operations ---//
type BuildTuple<
L extends number,
T extends any[] = [],
F = unknown,
> = `${L}` extends `-${number}`
? never
: T["length"] extends L
? T
: BuildTuple<L, [...T, F], F>;
type IsNegative<T extends number> = `${T}` extends `-${number}` ? true : false;
type Abs<T extends number> = `${T}` extends `-${infer N extends number}`
? N
: T;
type FlipSign<T extends number> = `${T}` extends `${0}`
? 0
: `${T}` extends `-${infer N extends number}`
? N
: `-${T}` extends `${infer N extends number}`
? N
: never;
type CutStarting<T extends any[], N extends number> = T extends [
...BuildTuple<N>,
...infer Rest,
]
? Rest
: [];
type CutEnding<T extends any[], N extends number> = T extends [
...infer Rest,
...BuildTuple<N>,
]
? Rest
: [];
type GetEnding<T extends any[], N extends number> = T extends [
...CutEnding<T, N>,
...infer Rest,
]
? Rest
: [];
type GetStarting<T extends any[], N extends number> = T extends [
...infer Rest,
...CutStarting<T, N>,
]
? Rest
: [];
type SliceStart<Tuple extends any[], StartIndex extends number> =
IsNegative<StartIndex> extends true
? GetEnding<Tuple, Abs<StartIndex>>
: CutStarting<Tuple, StartIndex>;
type SliceEnd<Tuple extends any[], EndIndex extends number> =
IsNegative<EndIndex> extends true
? CutEnding<Tuple, Abs<EndIndex>>
: GetStarting<Tuple, EndIndex>;
type Slice<
Tuple extends any[],
StartIndex extends number,
EndIndex extends number = Tuple["length"],
> =
SliceStart<Tuple, StartIndex> extends infer S extends any[]
? IsNegative<EndIndex> extends true
? SliceEnd<S, Abs<EndIndex>>
: Subtract<Tuple["length"], EndIndex> extends 0
? S
: SliceEnd<S, ParseNumber<`-${Subtract<Tuple["length"], EndIndex>}`>>
: [];
type Nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// [].slice(4, 7)
type Sliced = Slice<Nums, 4, 7>; // => [4, 5, 6]
// [].slice(-2)
type Sliced2 = Slice<Nums, -3, 10>; // => [9, 10]
// [].slice(-2, -1)
type Sliced3 = Slice<Nums, -2, -1>; // => [9]
//--- String operations ---//
type StringToTuple<
T extends string,
Result extends any[] = [],
> = T extends `${infer F}${infer R}`
? StringToTuple<R, [...Result, F]>
: Result;
type TupleToString<T extends any[], Result extends string = ""> = T extends [
infer F extends string,
...infer R,
]
? TupleToString<R, `${Result}${F}`>
: Result;
type StringLength<
T extends string,
Acc extends any[] = [],
> = T extends `${string}${infer R}`
? StringLength<R, [...Acc, unknown]>
: Acc["length"];
type SliceString<
T extends string,
Start extends number,
End extends number = StringLength<T>,
> = TupleToString<Slice<StringToTuple<T>, Start, End>>;
type RepeatString<
T extends string,
C extends number,
Result extends string = "",
Acc extends any[] = [],
> = Acc["length"] extends C
? Result
: RepeatString<T, C, `${Result}${T}`, [...Acc, unknown]>;
//--- Number operations ---//
type NumberPrimitive = number | bigint;
type SerializablePrimitive =
| NumberPrimitive
| string
| boolean
| null
| undefined;
type ParseBigInt<T extends SerializablePrimitive> =
`${T}` extends `${infer N extends bigint}` ? N : never;
type ParseNumber<T extends SerializablePrimitive> =
`${T}` extends `${infer N extends number}` ? N : never;
type Add<A extends number, B extends number> = [
...BuildTuple<A>,
...BuildTuple<B>,
]["length"] &
number;
// ]["length"];
type Subtract<A extends number, B extends number> =
BuildTuple<A> extends [...BuildTuple<B>, ...infer Rest]
? Rest["length"]
: ParseNumber<`-${Subtract<B, A>}`>;
type SubTest = Subtract<5, 2>; // =>
// M = Mantissa
// E = Exponent
// I = Integer part
// F = Fractional part
// D = Fractional decimals
type ParseNumberish<T extends SerializablePrimitive> = T extends NumberPrimitive
? T
: // Not a number primitive
number extends ParseNumber<T>
? // Complex number expression
T extends `${infer M}e${infer E extends number}`
? // Scientific notation
M extends `${infer I}.${infer F}`
? // Decimal number
StringLength<F> extends infer D extends number
? // Successfully calculated decimal length
IsNegative<E> extends true
? // Negative exponent
Add<D, Abs<E>> extends infer S extends number
? // Added decimal length to exponent to get new decimal index from right
ParseNumber<`${SliceString<`${I}${F}`, 0, ParseNumber<`-${S}`>>}.${SliceString<F, E>}`>
: // Failed to ddd decimal length to exponent
never
: Subtract<E, D> extends infer S extends number
? IsNegative<S> extends true
? // Decimal with exponent, e.g. 1.123e2
ParseNumber<`${SliceString<`${I}${F}`, 0, Add<D, E>>}.${SliceString<F, E>}`>
: // // Debug
// {
// slice: SliceString<`${I}${F}`, 0, Add<D, E>>;
// result: ParseNumber<`${SliceString<`${I}${F}`, 0, Add<D, E>>}.${SliceString<F, E>}`>;
// interger: I;
// fraction: F;
// decimals: D;
// exponent: E;
// }
// Integer expressed as a decimal number and exponent, e.g. 1.5e18
ParseBigInt<`${I}${F}${RepeatString<"0", S>}`>
: // Failed to subtract decimal length from exponent
never
: // Failed to calculate string length
never
: // Integer with exponent
ParseBigInt<`${M}${RepeatString<"0", E>}`>
: // NaN or unsupported number expression
number
: // Simple number or integer expression
T extends `${string}.${string}`
? // Parsable number
ParseNumber<T>
: // Parsable Integer
ParseBigInt<T>;
type s = ParseNumberish<"123">;
// ^?
type b1 = ParseNumberish<123n>;
// ^?
type n1 = ParseNumberish<123>;
// ^?
type d = ParseNumberish<1.23>;
// ^?
type ds = ParseNumberish<"1.23">;
// ^?
type eds = ParseNumberish<"12e3">;
// ^?
type eds2 = ParseNumberish<"1.12e4">;
// ^?
type eds3 = ParseNumberish<"-1.12e18">;
// ^?
//FIXME: should be 112.3
type eds4 = ParseNumberish<"1.123e2">;
// ^?
//FIXME: should be 1.231
type eds5 = ParseNumberish<"123.1e-2">;
// ^?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment