Ritik Mishra
Ritik Mishra

Reputation: 718

Is there a way to add two numbers together on the type level in typescript?

Suppose I had the following types

type One = 1;
type SmallEvens = 0 | 2 | 4 | 6 | 8 | 10;
type Three = 3;

Is there some kind of type Add<T, U> I could define that adds numbers on the type level like so?

type SmallOdds = Add<One, SmallEvens>; // same as `1 | 3 | 5 | 7 | 9 | 11`
type Four = Add<One, Three> // same as `4`

Alternatively, should I be looking at alternative representations of numbers to achieve this effect? However, being able to convert to something that extends number so that I can use it for tuple indexing would be a big plus.

Upvotes: 6

Views: 2334

Answers (2)

Arielhhs
Arielhhs

Reputation: 106

Update 2022

I have written a package which handles your issue (and a pretty much all kinds of type level arithmetic), and which completely avoids the "Type instantiation is excessively deep and possibly infinite" limitation.

https://www.npmjs.com/package/ts-arithmetic

Here are some examples of what you can do with this package:

import { Add, Subtract, Multiply, Divide, Pow, Compare, Mod } from 'ts-arithmetic'
// Check the docs below for more

// Add any two numbers
type AddExample = Add<1024, 256>
// 1280

// Subtract any two numbers
type SubtractExample = Subtract<400, 1000>
// -600

// Multiply any two numbers
type MultiplyExample = Multiply<25, 50>
// 1250

// Divide any two numbers
type DivideExample = Divide<-1, 800>
// -0.00125

// Raise a number to an integer power
type PowExample = Pow<5, 7>
// 78125

// Compare any two numbers (same rules as JavaScript Array.sort())
type CompareExample = Compare<123456, 20>
// 1

// Get the JavaScript mod (i.e. remainder)
type ModExmaple = Mod<87, 7>
// 3

Upvotes: 7

lawrence-witt
lawrence-witt

Reputation: 9354

After some light searching I found this fascinating article - Implementing Arithmetic Within TypeScript’s Type System - which does demonstrate a way to achieve what you're attempting using some cutting edge features.

We can create a type which adds two numbers together as follows:

type Length<T extends any[]> = 
    T extends { length: infer L } ? L : never;

type BuildTuple<L extends number, T extends any[] = []> = 
    T extends { length: L } ? T : BuildTuple<L, [...T, any]>;

type Add<A extends number, B extends number> = 
    Length<[...BuildTuple<A>, ...BuildTuple<B>]>;

type Seven = Add<3, 4> // 7

I'd recommend reading the article for the author's explanation (and many more arithmetic types). However, in a nutshell:

The type Length infers the length property of an array type which is passed to it. Where that array type is a tuple, it will provide the actual length rather than just number.

Length<[number, number, string]> // 3

The BuildTuple type will create a tuple type of length L populated with the type any. This works by using the spread operator to recursively increment the length of the tuple until it matches the conditional for length: L, at which point it will be returned.

BuildTuple<3> // [any, any, any]

The Add type should now be fairly self-explanatory. We build out tuples to the two lengths of the two numbers provided, then spread them to a new tuple and get the combined length.

While the author does not include adding onto union types, we can do that fairly easily ourselves with a mapped type:

type MappedAdd<A extends number, B extends number> = {
  [key in B]: Add<A, key>
}[B]

type One = 1;
type SmallEvens = 0 | 2 | 4 | 6 | 8 | 10;

type SmallOdds = MappedAdd<One, SmallEvens>; // 1 | 3 | 5 | 7 | 9 | 11

For this use case it works great. However, eventually the recursivenss of the tuple creating type will become too deep, limiting this to numbers under 45 (or 44 for the mapped version):

Add<1, 45> // Type instantiation is excessively deep and possibly infinite.

Upvotes: 7

Related Questions