TypeScriptにInfinity型は存在する?

2024/08/16に公開1

TypeScriptにはリテラル型という機能があるので、数値を型として使うことができます。
では特殊な数値Infinityに対応するInfinity型は存在するのか? がこの記事のテーマです。

結論だけ先に書くと「Infinity型は表面上は提供されていないが内部的には存在する」という感じの不思議な状況のようです。
ちょっとややこしい話なので、順を追って説明していきます。

そもそもInfinityはリテラルではない

実はInfinityはグローバル変数らしいです。
試しにVSCodeでInfinityの定義にジャンプしてみると次のように型定義されています。

このことから次のことが分かります。

  1. Infinityはリテラルではないので、リテラル型としてのInfinity型も存在しない
  2. 変数Intifityの型はnumberとして定義されているので、typeof InfinityをInfinity型として使うこともできない

Infinityという名前の型は標準で定義されてない

Infinityという名前の組み込み型が用意されていないことも確認しておきましょう。
おもむろにInfinity型を使おうとすると次のように型エラーが出ます。

(逆に言うと、Infinityという名前の型はユーザーが自由に定義できちゃったりします)

さて、ここまでの話だとTypeScriptという言語はInfinity型のことなど一切気にしていなさそうに思えますが…🤔

TypeScript処理系内部にはInfinity型が存在する

少し状況をイメージしにくいかもしれませんが、実はTypeScript処理系にはInfinity型が内部的に存在する痕跡があります。
こちらのスクリーンショットをご覧ください。
ソースコード内には一切Infinityと書いていないのに、メッセージ内にInfinityという型名が出てきます

つまり、TypeScript処理系は上記の1e+999という型を、内部的にInfinity型として扱っているわけです。

TypeScriptにおける指数表記

1e+999はいわゆる指数表記で、1の後に0が999個続く超巨大な数値です。
数式で書くと1 \times 10^{999}ですね。

TypeScript/JavaScriptではこの表記で数値リテラルを書くことができます。

console.log(1e+3) // 1000
console.log(2.5e+5) // 250000

ちなみにソースコード上の表記が指数表記であろうとなかろうと、一定の大きさを超える数値は指数表記で表示されることを頭の片隅の置いておくと良いかもしれません。

console.log(1e+20) // 100000000000000000000
console.log(1e+21) // 1e+21
console.log(1000000000000000000000) // 1e+21

number型の上限値とInfinityの関係

TypeScriptのnumber型が扱える数値の範囲には次のような上限があります。

console.log(Number.MAX_VALUE) // 1.7976931348623157e+308

実はこの上限値1.7976931348623157e+308より大きい数値リテラルを書くと、Infinityになります。
下記のコードで実際に確認できます。

console.log(1.8e+308) // Infinity
console.log(1.8e+308 === Infinity) // true
console.log(1.8e+308 === 1e+999) // true

上記は値レベルの話ですが、型レベル(数値リテラル型)でも全く同じ現象が起こります。
つまり、1.7976931348623157e+308より大きいリテラル型を書くと、Infinity型になるわけです。
先ほど掲載していたスクリーンショットの中で1e+999というリテラル型がInfinity型と表示されたのがその証拠です。

Infinity型をユーザーが定義するとどうなるか

ここまでの話を踏まえると、例えば次のようにInfinity型を定義できます。

export type Infinity = 1e+999

これで問題解決…と言いたいところですが、実は負の無限大-Infinity型の方は全然うまくいきません。
まず、型の世界では-演算子はサポートされていません。使おうとすると次のようにエラーになってしまいます。

また-Infinityという名前の型を直接定義することも不可能です。-は識別子に使えない文字だからです。

export type -Infinity = -1e+999 // ❌
export type NegativeInfinity = -1e+999 // ✅

そういう問題があるので、Infinity型をユーザーが定義しても少し不満が残りそうです。
やはり公式にサポートしてもらわないと解決できそうにないです。

TypeScript公式リポジトリのissue

Infinity型が無い件の公式issueは何年も前からあるようですが、残念ながら大きな動きはありません😢
https://github.com/microsoft/TypeScript/issues/32277
https://github.com/microsoft/TypeScript/issues/15135

しかしハイレベルな型付けを実用するライブラリなどがここ数年でも増えていると思うので、Infinity型サポートの需要が高まって新たな動きが生まれる可能性も0ではない気がします。

多対多の関係を表現するときなどに使える0 | 1 | Infinity型や、timeout引数にInfinityを与えた時の特別な挙動を型レベルで表現するなど、一応用途はあると思うので個人的にはこっそり期待しています。

chot Inc. tech blog

Discussion

smikitkysmikitky

そのIssueを立てた本人ですが、全然動きがなくてもどかしいですね…。

正確には「内部にInfinity型が存在する」というわけではないと思っています。const hoge: 1e+999 = 42 のようなことをすると、途中までは内部的には単なるnumberのリテラル型として扱おうとします。そして、1e+999 という型を文字列で表現しようとする際の変換で、ぱっと見ではまるで内部に特別なInfinity型が存在するように見えてしまっている…というだけの話だと思います。

それでも無理矢理作ったこの型が「事実上のInfinity型」として期待通りに動いてくれれば良いのですが、実際にはこの型はまともに動作しません。

type Five = 5;
type Mugen = 1e999; // ぱっと見Infinityっぽい型にはなるが…

let x: 1 | Five = 5;
// →これはOK

let y: 1 | Mugen = Infinity;
// →なぜかエラー: Type 'number' is not assignable to type '1 | Infinity'.

let z1: 1 | typeof Infinity = 3.14;
// →これはエラーにならない (typeof Infinity は単なる number だから)

const a = 1e999;
let z2: 1 | typeof a = 3.14;
// →これはエラー
let z3: 1 | typeof a = Infinity;
// →これもエラー

ちなみに、「Infinitytruefalsenull と違い、厳密にはリテラルではない」というのは全くその通りなのですが、それは undefined も同様です。InfinityundefinedNaN も、「演算の結果で生まれることがある特別な値だが、仕様上は特別な値が入ったグローバル定数に過ぎない」という意味では同様です。undefined をリテラル型として扱える以上、Infinity もそうできないのは純粋に奇妙な制限だなと思っています…。