How do I assert a `number` type is positive?

Viewed 507

Let's say I have the following function for finding the square root of a number:

function sqrt(n: number): number {
    return Math.sqrt(n);
}

I would like to find a way to assert that the given number is non-negative. Currently, the Math.sqrt function returns NaN for negative numbers which can introduce sneaky bugs so I'd rather catch these simply errors at compile-time. Is there any way to make TypeScript error when the given number is not positive?

1 Answers

You can do this via some string intrinsics and conditional types. Basically, we can take in a generic for the number, N. Then, stringify that number type via ${N} and check if that string begins with a - meaning that the number is in fact negative. If so, we can return never to force TypeScript to fail on the generic argument.

type AssertPositive<N extends number> =
  number extends N ? N : `${N}` extends `-${string}` ? never : N;

function sqrt<N extends number>(n: AssertPositive<N>): number {
    return Math.sqrt(n);
}

// No Errors 
sqrt(5);
sqrt(0);
sqrt(1.5);
sqrt(1e3);

// Correctly errors 
sqrt(-5);
sqrt(-1);
sqrt(-3.4);
sqrt(-0.4);

TypeScript Playground Link

The number extends N ? N clause of AssertPositive was simply to make AssertPositive allow non-exact numeric types (number) to work with sqrt because there is no way to make sure the number type is positive / negative as it provides no context.

Force Cast for number type (credits @jcalz)

As @jcalz mentioned in the comments, you may want to force an explicit cast when providing a number type to sqrt. This can be done via branded types which basically attach some context to the number type itself. Here is the playground link that @jcalz created to showcase this behavior.

Related