When it comes to JavaScript and TypeScript, the type system does not provide explicit support for various number types beyond number
and bigint
.
For dealing with integers in particular, TypeScript offers the number
and bigint
types to ensure compile-time checks that a number is indeed an integer, as illustrated in this example.
In certain scenarios where specific numbers are required, a literal union type can be utilized. For instance:
type PowerOfTwo = 1 | 2 | 4 | 8 | 16; // and so forth
An alternative method, applicable to other numeric sub-types as well, involves using a runtime check to verify that a number belongs to a given sub-type. By incorporating nominal typing along with an assertion function, TypeScript can acknowledge that such validation has been performed. For integers, this could involve utilizing Number.isInteger()
for the runtime check:
type Int = number & { _brand: never };
function assertInt(n: number): asserts n is Int {
if(!Number.isInteger(n)) {
throw new Error(`${n} is not integer`);
}
}
This assertion function can then be employed to create functions like integer division:
function divide(dividend: number, divisor: number): Int {
const quotient = Math.floor(dividend / divisor);
assertInt(quotient);
return quotient;
}
const x = divide(5, 2); // x now has type Int
A wrapper function can also be crafted for inline checks:
function checkInt(n: number): Int {
assertInt(n);
return n;
}
Such a function proves useful when calling methods requiring parameters of type Int
:
function multiply(multiplicand: Int, multiplier: Int): Int {
const product = multiplicand * multiplier;
assertInt(product);
return product;
}
const y = multiply(checkInt(2), checkInt(4)); // y is of type Int
Playground link
TL;DR Numeric types have limited support in JavaScript and TypeScript. The distinction between different types mostly occurs at runtime, but nominal types and assertion functions can assist in convincing the TypeScript compiler about the true sub-type of a number.