TypeScriptの `number` 型と数値リテラルの深淵:実務で「ハマらない」ための境界線
やあ。コードレビューをしていて「あ、ここ危ないな」と直感的に感じるポイントがあるんだ。特に `number` 型。みんな軽く扱いがちだけど、TypeScriptにおける `number` は、JavaScriptの仕様である「IEEE 754 浮動小数点数」という巨大な氷山の一部に過ぎない。
今日は、中級エンジニアの君が「なんとなく」で書いている数値の扱いを、現場で「絶対にバグらせない」レベルまで引き上げるための話をしよう。
—
1. `number` 型の正体:JavaScriptの「すべて」を抱きしめる広さ
TypeScriptにおいて `number` を定義するということは、JavaScriptの `Number` オブジェクトが扱うあらゆる数値を許可することを意味する。
これには、整数や浮動小数点数だけでなく、`NaN` (Not a Number) や `Infinity` も含まれる。実務で一番怖いのは「APIから値が返ってくるはずが、計算過程で `NaN` になり、それがそのままUIに表示されてしまう」というケースだ。
// 現場でよくある「うっかり」
const calculateDiscount = (price: number, rate: number): number => {
return price rate;
};
// 0除算や不正な計算が混じると、TypeScriptはエラーを吐かないのに結果は NaN になる
const result = calculateDiscount(1000, 0 / 0);
console.log(result); // NaN が出力される
シニアからのアドバイス:
`number` をそのまま信用してはいけない。計算の境界値や、APIレスポンスのバリデーションには必ず `Number.isFinite()` を通す癖をつけよう。`NaN` や `Infinity` をフロントエンドのロジックに紛れ込ませないことが、堅牢なUIへの第一歩だ。
—
2. 数値リテラル型:型安全の「最強の制約」
中級者がステップアップするためにぜひ使ってほしいのが「数値リテラル型」だ。これは、単なる `number` ではなく、「この値しか許さない」という強力な制約を設ける手法だ。
例えば、UIコンポーネントのプロパティや、特定のステータスコードを扱う際に非常に効果的だ。
// 許容される値をリテラル型で定義する
type StatusCode = 200 | 400 | 401 | 500;
const handleResponse = (code: StatusCode) => {
// ここでは 200, 400, 401, 500 以外はコンパイルエラーになる
console.log(`現在のステータス: ${code}`);
};
handleResponse(200); // OK
// handleResponse(201); // コンパイルエラー: Argument of type ‘201’ is not assignable to parameter of type ‘StatusCode’.
これを活用すれば、`if` 文や `switch` 文での網羅性チェック(Exhaustiveness Checking)も強力に機能するようになる。
—
3. `number` と `BigInt` の決定的な違い
最近のフロントエンドでは、WebAssemblyとの連携や、高精度な暗号処理、あるいは巨大なIDの取り扱いで `BigInt` を見る機会が増えてきたはずだ。
ここで絶対に覚えておいてほしいのは、`number` と `BigInt` は型として完全に別物だということ。暗黙的な型変換は発生しない。
const num: number = 10;
const big: bigint = 10n;
// num + big; // コンパイルエラー! (Cannot mix ‘bigint’ and other types)
// 明示的な変換が必要
const result = BigInt(num) + big; // OK
なぜ別物なのか? それは精度が違うからだ。`number` は `2^53 – 1` を超えると精度を失うが、`BigInt` はメモリが許す限り無限に数値を扱える。実務で「IDが巨大すぎて末尾が0になる」というバグに遭遇したら、迷わず `BigInt`(あるいは文字列での管理)を検討してくれ。
—
4. 実務で役立つ「数値バリデーション」のベストプラクティス
最後に、現場でコピペして使える、数値の安全な取り扱いパターンを共有しておくよ。
/
- 安全に数値へ変換するユーティリティ
- APIからのレスポンスなど、信頼できない値に対して使う
/
const safeParseNumber = (value: unknown): number | null => {
const parsed = Number(value);
// NaNを除外し、かつ有限数であることを保証する
if (Number.isFinite(parsed)) {
return parsed;
}
return null; // 不正な値ならnullを返すことで、呼び出し元にハンドリングを強制する
};
// 使い方
const apiResponse = “123.45”;
const validatedPrice = safeParseNumber(apiResponse);
if (validatedPrice !== null) {
// ここでは確実に number 型として扱える
console.log(validatedPrice.toFixed(2));
}
まとめ:TypeScriptは「守ってくれる」のではなく「気づかせてくれる」
TypeScriptの `number` 型を攻略するコツは、「JavaScriptの緩い仕様を、TypeScriptの型システムでどれだけ厳格に囲い込めるか」にかかっている。
- `number` は `Infinity` や `NaN` を含む「広すぎる」型であると意識する。
- 特定の値が必要なら、リテラル型で徹底的に絞り込む。
- 巨大な数値や精度の厳密さが求められるなら、迷わず `BigInt` へ。
型定義は、単なるドキュメントじゃない。君が書くロジックを守るための「防波堤」なんだ。明日からのコードレビューで、誰かの `number` の使い方が少しでも危うかったら、この話を思い出してアドバイスしてあげてほしい。
君の書くコードが、もっと堅牢で、もっと美しいものになることを期待しているよ。

コメント