Repository navigation
TS FiniteNumber Helper type
This page explains how to implement a FiniteNumber type helper in TypeScript, all the code and examples in this page can be also found in the ts-finite.ts file.
Important
I'm still relatively new to TypeScript, so please don't take the information I provide at face value, and always question stuff, maybe I made a mistake somewhere, or I was wrong about something, I'll happily accept any criticizm or corrections, so feel free to open a PR for that.
From the discussion in this GitHub typescript issue, I commented an implementation of the Not helper type with some usage examples (you can find more about it here), and this comment asked if there is a way to use that Not type I showed to create a FiniteNumber type that will be typed as
number not (Infinity | -Infinity | NaN)
This page is an answer to that comment, as the implementation of FiniteNumber type is not directly possible using just the Not helper type.
A Finite Number means a number that isn't Infinity or -Infinity or NaN, in typescript, there isn't yet a FiniteNumber type, and there isn't yet a straightforward way to create one, as Infinity, -Infinity and NaN are not separate types or literal types yet, and are all inferred as just number by typescript. This means that even if there was a not keyword or type helper, we wouldn't be able to implement FiniteNumber.
We can in fact use this current limitation of typescript to our advantage to create a LiteralNumber type.
We can use the fact that Infinity and NaN are not literal types to create a LiteralNumber type that will represent all numbers that can be of literal type, because (how I understand it) the only number values that cannot yet be literal types are Infinity and NaN.
type LiteralNumber<T> = T extends number ? (
number extends T ? never : T
) : never;This type alone should be sufficient to represent a FiniteNumber type, the obvious issue with it tho, is that values passed to it have to be inferred as literal types, if it gets inferred as the general number type, it will give never.
There is also a weird behaviour in typescript that we need to take into account.
Any literal number n, as n ≥ 1e309 and n ≤ (-1e309), is inferred as Infinity and -Infinity respectively by typescript (yeah, even tho Infinity is not a type) and are also number literals, which is a case that our LiteralNumber doesn't include.
Tho, fortunately, this is very easy to include by creating an Infinity type from those literal values: (inspired by this comment)
type Infinity = 1e309 | -1e309;Now, using the Not type helper from here, we can create a FiniteNumber type:
// T extends LiteralNumber AND T does not extend Infinity
type FiniteNumber<T> = Not<Infinity, T, LiteralNumber<T>>;In fact, in this specific case, the type helper Not is not really needed, and you can just replace LiteralNumber by one type helper that includes the Infinity condition.
type FiniteNumber<T> = T extends number ? (
number extends T ? never : (
T extends Infinity ? never : T
)
) : never;I prefer the second implementation as it avoids unnecessary dependencies.
const takesFiniteNumber = <T extends number>(n: FiniteNumber<T>) => n;
takesFiniteNumber(''); // gives type error as it isn't a number
takesFiniteNumber(10);
takesFiniteNumber(3.14);
takesFiniteNumber(-42.5);
takesFiniteNumber(1e6);
takesFiniteNumber(1e999); // gives type error as it is inferred as `Infinity`
takesFiniteNumber(-1e999); // gives type error as it is inferred as `-Infinity`
takesFiniteNumber(Infinity); // gives type error as it is inferred as `number`
takesFiniteNumber(-Infinity); // gives type error as it is inferred as `number`
takesFiniteNumber(NaN); // gives type error as it is inferred as `number`All above examples give type errors correctly for non-finite number values.
As stated before, this solution relies on the fact that infinity and NaN are inferred as just number, and so passing a type number to FiniteNumber will give never as expected, meaning that the implementation of FiniteNumber needs to be able to infer the value of the number as a literal for it to works.
// This won't work as expected
let value1 = 20; // value1 is inferred as just `number`.
let num1: FiniteNumber<typeof value1> = value1; // won't work as `typeof value1` is `number` and not the literal `20`.
// This will work
const value2 = 20; // value2 is inferred as the literal `20`.
let num2: FiniteNumber<typeof value2> = value2; // works because `typeof value2` is the literal `20`.Prompted by this comment, that shared that a solution for Finite number type already exists in the library type-fest, that I didn't know about, I started looking into it, to see if it is the same, and if I just wasted my time reinventing the wheel.
But after comparing both solutions, it seems that my implementation is closer to a true Finite number type and has fewer limitations.
The solution provided by type-fest uses the same trick shared by this comment, that I also used to create the Infinity type to address the weird behaviour by typescript, that solution tho creates Finite type using only this Infinity type, which means that it will only not accept a finite number value that is above the max limit to be considered Infinity, and it will not give a type error if you pass to it the values Infinity, -Infinity or NaN. See below code:
While my implementation works for all those cases (taken into account the one limitation stated before).
It depends on your needs and the limitations that you are comfortable accepting, in the case of this solution you need to accept the fact that passing the generic number type won't work, tho from my usage, this isn't that big of a deal because you already have to infer the value of a number to use the type helper, and in the majority of cases, that means that the number will be inferred as a type literal rather than the generic number type.
While if you're using type-fets's Finite type, you need to accept the limitation that the function or variable you set that type on, will accept the values Infinity, -Infinity and NaN, which in my opinion defeats the purpose of enforcing a Finite number type.