TypeScriptは、JavaScriptが持つ数値の柔軟性を維持しながら、静的な型チェックによって開発の安全性を劇的に向上させるプログラミング言語です。
現代のウェブ開発において、金銭計算や複雑なデータ処理を行う際、数値の型を正しく扱うことはランタイムエラーを防ぐための第一歩となります。
本記事では、TypeScriptにおけるnumber型の基本から、2026年現在のモダンな開発環境で求められるBigInt型との使い分けまでを詳しく見ていきましょう。
数値リテラルや型推論、さらには高度な型定義の手法を学ぶことで、より堅牢なコードを書くスキルを身につけることができます。
TypeScriptにおけるnumber型の基本概念
TypeScriptのnumber型は、JavaScriptと同様に64ビット浮動小数点数(IEEE 754形式)として扱われます。
これは、整数と小数の両方を同じnumber型で表現することを意味しています。
基本的な変数の宣言では、以下のように記述します。
// 明示的な型指定による数値の宣言
const age: number = 25;
const price: number = 1200.50;
// 型推論を利用した宣言
let count = 10; // 自動的にnumber型として推論される
TypeScriptは強力な型推論を持っているため、初期値がある場合は明示的に: numberと書かなくても適切な型が割り当てられます。
しかし、関数の引数や戻り値などでは、意図しない型が混入するのを防ぐために型を明記することが一般的です。
また、number型には通常の数値だけでなく、特殊な数値も含まれます。
NaN(Not a Number)やInfinity(無限大)も、型システム上はnumber型として扱われる点に注意が必要です。
const invalidValue: number = NaN;
const infiniteValue: number = Infinity;
console.log(typeof invalidValue); // "number"が出力される
number
これらの特殊な数値が混じると計算結果が狂う原因となるため、「数値であること」と「有効な計算対象であること」は別問題として考える必要があります。
数値リテラルの柔軟な表記法
TypeScriptでは、10進数以外の数値表記も直感的に記述することが可能です。
2進数、8進数、16進数のリテラルをサポートしており、低レイヤーの処理やフラグ管理において非常に役立ちます。
let binary: number = 0b1010; // 2進数
let octal: number = 0o744; // 8進数
let hex: number = 0xf00d; // 16進数
console.log(binary, octal, hex);
10 484 61453
また、大きな数値を扱う際の視認性を高めるために、数値区切り文字(Numeric Separators)を使用できます。
アンダースコア(_)を数字の間に挟むことで、桁数の多い数値を読みやすく記述できます。
const budget: number = 1_000_000_000; // 10億
const fee: number = 1_500.50; // 1,500.50
このアンダースコアは、コンパイル時に削除されるため、実際の動作に影響を与えることはありません。
コードの可読性を維持し、桁の間違えによるバグを防ぐために積極的に活用すべき機能です。
number型とBigInt型の決定的な違い
JavaScriptおよびTypeScriptには、通常のnumber型では扱いきれない巨大な整数を扱うためのbigint型が存在します。
number型が安全に扱える整数の範囲は、Number.MAX_SAFE_INTEGER(2の53乗マイナス1)までです。
これを超える数値をnumber型で扱うと、精度が失われ、計算結果が不正確になるリスクがあります。
const max = Number.MAX_SAFE_INTEGER;
console.log(max + 1 === max + 2); // trueになってしまう(精度の限界)
true
このような巨大な数値を正確に扱うために、bigint型を使用します。
bigint型を使用するには、数値の末尾にnを付けるか、BigInt()関数を呼び出します。
const hugeInt: bigint = 9007199254740991n;
const anotherHugeInt: bigint = BigInt("9007199254740991");
numberとBigIntの混在に関するルール
TypeScriptでは、number型とbigint型の演算を直接行うことはできません。
これらを混ぜて計算しようとすると、コンパイルエラーが発生します。
const n: number = 10;
const b: bigint = 20n;
// const result = n + b; // エラー: Operator '+' cannot be applied to types 'number' and 'bigint'.
計算を行う場合は、明示的にどちらかの型へ変換する必要があります。
ただし、bigintからnumberへ変換する際は、精度が落ちる可能性があることを常に意識しなければなりません。
| 特徴 | number型 | bigint型 |
|---|---|---|
| データ形式 | 64ビット浮動小数点数 | 任意の精度の整数 |
| 扱える範囲 | ±(2^53 – 1) の範囲で正確 | メモリの許す限り無制限 |
| 小数の扱い | 可能 | 不可能(整数のみ) |
| 主な用途 | 一般的な計算、UIの座標など | ID、高精度の金融計算、暗号化 |
数値リテラル型による厳格な制御
TypeScriptの強力な機能の一つに、数値リテラル型があります。
これは、単に「数値」という広い枠組みではなく、「特定の数値のみ」を許容する型を作成できる機能です。
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
let result: DiceRoll;
result = 3; // OK
// result = 7; // エラー: Type '7' is not assignable to type 'DiceRoll'.
この機能は、設定値の制限やステータスコードの管理において非常に有効です。
enum(列挙型)に近い使い方ができますが、リテラル型の方がより軽量で、TypeScriptの型システムと親和性が高いというメリットがあります。
読み取り専用の数値
配列やオブジェクトのプロパティとして数値を扱う際、値を変更させたくない場合はreadonly修飾子を使用します。
interface Config {
readonly maxRetryCount: number;
}
const settings: Config = { maxRetryCount: 5 };
// settings.maxRetryCount = 10; // エラー: Cannot assign to 'maxRetryCount' because it is a read-only property.
定数としての安全性を高めることで、大規模なアプリケーションでも意図しない書き換えを未然に防ぐことができます。
数値への変換と型ガード
外部APIやユーザー入力から得られるデータは、文字列として渡されることが多々あります。
これらを安全にnumber型として扱うための手法を確認しておきましょう。
基本的な変換方法
最も一般的な方法は、Number()関数、parseInt()、parseFloat()、または単項プラス演算子(+)を使用することです。
const input = "123.45";
const num1 = Number(input); // 123.45
const num2 = parseFloat(input); // 123.45
const num3 = +input; // 123.45
ただし、これらの変換には注意が必要です。
変換に失敗した場合(例えば数値以外の文字列を渡した場合)、結果はNaNになりますが、TypeScriptの型システム上は依然としてnumber型として扱われます。
安全な型チェックの実装
実行時に値が本当に計算可能な数値であるかを判定するには、Number.isNaN()やtypeof演算子を組み合わせた型ガードを利用します。
function processValue(val: unknown) {
if (typeof val === "number" && !Number.isNaN(val)) {
// ここではvalが「有効なnumber型」であることが保証される
console.log(val.toFixed(2));
} else {
console.error("無効な数値です");
}
}
unknown型で受け取ったデータを処理する場合、このように実行時のチェックを丁寧に行うことが、TypeScriptの型安全性を最大限に活かすコツです。
浮動小数点演算の落とし穴と対処法
TypeScript(JavaScript)のnumber型を扱う上で避けて通れないのが、浮動小数点数特有の計算誤差です。
有名な例として、0.1 + 0.2の結果が0.3にならないという問題があります。
console.log(0.1 + 0.2);
0.30000000000000004
これはIEEE 754形式で数値をバイナリ変換する際に発生する避けられない誤差です。
解決策として、以下のようないくつかの戦略が考えられます。
- 整数に変換して計算する(100倍して計算した後に100で割るなど)。
toFixed()やMath.round()を使用して、表示時に丸める。- 「decimal.js」や「big.js」などの高精度計算ライブラリを使用する。
2026年現在のモダンな開発では、金銭が絡む重要な計算においてライブラリを利用することが推奨されます。
単純なUIの表示調整であれば、標準のMathオブジェクトで十分対応可能です。
const price = 0.1 + 0.2;
const safePrice = Math.round(price * 100) / 100;
console.log(safePrice); // 0.3
高度なテクニック:Branded Typesによる数値の区別
TypeScriptでは、同じnumber型であっても「IDとしての数値」と「重さとしての数値」を区別したい場合があります。
これを実現するのがBranded Types(またはNominal Typing)と呼ばれる手法です。
type UserId = number & { readonly __brand: "UserId" };
type OrderId = number & { readonly __brand: "OrderId" };
function getUser(id: UserId) {
console.log(`Fetching user ${id}`);
}
const myUserId = 100 as UserId;
const myOrderId = 200 as OrderId;
getUser(myUserId); // OK
// getUser(myOrderId); // エラー: Argument of type 'OrderId' is not assignable to parameter of type 'UserId'.
この手法を用いると、ロジック層での型の取り違えを完全に防ぐことができます。
大規模なプロジェクトにおいて、単なるnumber型が多用されるとコードの意図が不透明になりがちですが、Branded Typesによってドメイン知識を型に組み込むことが可能になります。
まとめ
TypeScriptのnumber型は、単なる数値の器ではなく、非常に奥の深い機能を持っています。
基本的な浮動小数点の特性を理解しつつ、必要に応じてbigint型を選択する判断力が重要です。
また、数値リテラル型やBranded Typesを活用することで、ドメインに特化した厳格な型定義を実現できることも大きな強みです。
本記事で紹介したテクニックを駆使して、安全でメンテナンス性の高い数値計算ロジックを構築していきましょう。
精度の高いコードは、ユーザーにとっての信頼性につながるだけでなく、将来の自分やチームメンバーをバグから救うことにもつながります。
