JavaScriptの開発において、変数が「数値であるかどうか」を判定する作業は、一見シンプルに見えて非常に奥が深いテーマです。
JavaScriptは動的型付け言語であり、暗黙的な型変換が頻繁に行われるため、意図しない挙動を防ぐには適切な判定手法を選択しなければなりません。
例えば、NaN(Not-a-Number)という値もデータ型としては数値(number型)として扱われるため、初心者だけでなく経験豊富なエンジニアでもバグを作り込むことがあります。
本記事では、古典的な手法から、現在の開発現場で推奨されるモダンな判定テクニックまで、詳細に解説していきます。
最新のJavaScript仕様(ECMAScript)に基づいた最適な判別ロジックを身につけ、堅牢なコードを書けるようになりましょう。
なぜJavaScriptの数値判定は難しいのか
JavaScriptで数値判定を行う際に、まず直面するのがtypeof演算子の挙動です。
typeofを使えば変数の型を調べることができますが、これだけでは不十分なケースが多いのです。
// typeofによる数値判定の例
console.log(typeof 123);
console.log(typeof NaN);
console.log(typeof Infinity);
"number"
"number"
"number"
上記の通り、数値ではないはずのNaNや、無限大を表すInfinityもすべて“number”として判定されてしまいます。
実際のアプリケーション開発では、「有効な計算が可能な数値であること」を判定したい場合がほとんどです。
そのため、単なる型のチェックではなく、値の内容まで踏み込んだ判定が必要になります。
古典的な判定方法:isNaNとisFiniteの問題点
かつてのJavaScriptでは、グローバル関数のisNaN()やisFinite()が使われてきました。
しかし、これらの関数には強力すぎる「暗黙の型変換」という罠が存在します。
グローバルisNaN()の挙動
グローバルなisNaN()は、引数が数値でない場合に、まず数値に変換しようと試みます。
// グローバルisNaNの例
console.log(isNaN("123"));
console.log(isNaN("hello"));
console.log(isNaN(undefined));
false
true
true
文字列の”123″がfalse(数値ではない、ではない=数値である)と判定されるのは、自動的に数値の123に変換されるためです。
この挙動は便利な反面、意図しない文字列を数値として通してしまうリスクがあります。
グローバルisFinite()の挙動
同様に、グローバルなisFinite()も引数を数値に変換してから、それが有限数かどうかを判定します。
// グローバルisFiniteの例
console.log(isFinite("123"));
console.log(isFinite(null));
true
true
驚くべきことに、nullがtrueと判定されてしまいます。
これはNumber(null)が0になるというJavaScriptの仕様に引きずられているためです。
モダンな判定のスタンダード:Number.isFinite
現代のJavaScript開発において、最も信頼性が高く推奨されるのがNumber.isFinite()メソッドです。
このメソッドはグローバル関数版とは異なり、引数を数値に強制変換しません。
Number.isFiniteの厳密な判定
値が「number型」であり、かつ「NaNやInfinityではない有限数」である場合のみtrueを返します。
// Number.isFiniteの使用例
console.log(Number.isFinite(123));
console.log(Number.isFinite("123"));
console.log(Number.isFinite(NaN));
console.log(Number.isFinite(Infinity));
console.log(Number.isFinite(null));
true
false
false
false
false
このように、文字列やnull、特殊な数値定数をすべて正確に弾くことができます。
計算処理の前に「その変数が安全な数値かどうか」を確認するなら、このメソッドを使うのがベストプラクティスです。
NaNを特定して判定する:Number.isNaN
特定の処理結果がNaNになっていないかを確認したい場合は、Number.isNaN()を使用します。
これもNumber.isFinite()と同様に、暗黙の型変換を行わない厳密な判定が可能です。
// Number.isNaNによる厳密なNaN判定
console.log(Number.isNaN(NaN));
console.log(Number.isNaN("NaN"));
console.log(Number.isNaN(undefined));
true
false
false
「値そのものがNaNであること」だけをピンポイントで判定できるため、計算ミスや不正なデータ加工の検知に役立ちます。
整数かどうかを判定する方法
数値の中でも、特に「整数(Integer)」であることを保証したいケースがあります。
例えば、配列のインデックスやループのカウントなどは、小数であってはなりません。
Number.isInteger
値が整数かどうかを判定するには、Number.isInteger()を使用します。
// Number.isIntegerの例
console.log(Number.isInteger(10));
console.log(Number.isInteger(10.5));
console.log(Number.isInteger("10"));
true
false
false
注意点として、10.0のような値は数学的に整数とみなされるため、trueを返します。
安全な整数の判定:Number.isSafeInteger
JavaScriptの数値型(IEEE 754 倍精度浮動小数点数)で正確に扱える整数の範囲には限界があります。
その範囲は-(2^53 - 1)から2^53 - 1までです。
この範囲を超えた大きな整数を扱う場合、計算結果が不正確になる可能性があります。
// 非常に大きな数値の判定
const bigNum = 9007199254740992;
console.log(Number.isInteger(bigNum));
console.log(Number.isSafeInteger(bigNum));
true
false
精度が保証される範囲内かどうかをチェックするには、Number.isSafeInteger()が必須となります。
実務で役立つ数値判定比較表
これまでに紹介した各手法が、どのような入力値に対してどのような結果を返すのかを表にまとめました。
| 入力値 | typeof === “number” | isNaN() (Global) | Number.isFinite() | Number.isInteger() |
|---|---|---|---|---|
| 123 | true | false | true | true |
| “123” | false | false | false | false |
| NaN | true | true | false | false |
| Infinity | true | false | false | false |
| null | false | false | false | false |
この表からも、Number.isFinite()が最も「人間が直感的に数値だと感じるもの」を正確に捉えていることがわかります。
文字列形式の数値を判定したい場合
フォームの入力値やURLパラメータなど、「データ型は文字列だが、中身が数値として正しいか」を判定したい場合があります。
Number.isFinite()では文字列を受け取ると即座にfalseを返してしまうため、別の工夫が必要です。
正規表現による数値判定
文字列全体が数字だけで構成されているかをチェックするには、正規表現が有効です。
// 文字列が数値形式か判定する関数
function isNumericString(val) {
if (typeof val !== "string") return false;
// 整数または小数の形式にマッチするか
return /^-?\d+(\.\d+)?$/.test(val);
}
console.log(isNumericString("123.45"));
console.log(isNumericString("-50"));
console.log(isNumericString("abc"));
true
true
false
parseFloatを活用した手法
より柔軟に、空白や型変換を考慮しつつ厳密さを保ちたい場合は、以下のようなカスタム関数がよく使われます。
// 実践的な数値判定ロジック
function isRealNumber(val) {
return !isNaN(parseFloat(val)) && isFinite(val);
}
console.log(isRealNumber(" 100 ")); // 空白があってもOK
console.log(isRealNumber("")); // 空文字はNG
true
false
BigInt(大きな整数)の登場と判定
JavaScriptには、通常の数値型(Number)以外に、巨大な整数を扱うためのBigInt型が存在します。
BigIntは数値の一部ですが、Number型とは厳密に区別されるため、判定時には注意が必要です。
// BigIntの判定
const bigValue = 100n;
console.log(typeof bigValue);
console.log(Number.isFinite(bigValue));
"bigint"
false
Number.isFinite()はBigIntに対してfalseを返します。
もし、Number型とBigInt型の両方を「数値」として許容したい場合は、以下のような判定式を検討してください。
function isAnyNumber(val) {
return typeof val === "number" || typeof val === "bigint";
}
ただし、BigIntとNumberを混合して計算するとエラーが発生するため、判定後に適切に処理を分岐させる必要があります。
【応用】TypeScriptでの数値判定と型ガード
現代の開発ではTypeScriptを利用することが一般的です。
TypeScriptでは、判定の結果をその後のコードの「型推論」に活かすことができます。
これを「型ガード(Type Guard)」と呼びます。
// TypeScriptでのユーザー定義型ガード
function isNumber(val: unknown): val is number {
return typeof val === "number" && Number.isFinite(val);
}
let data: unknown = 42;
if (isNumber(data)) {
// ここではdataは確実にnumber型として扱える
console.log(data.toFixed(2));
}
このように判定ロジックをカプセル化することで、プロジェクト全体で安全かつ統一された数値判定が可能になります。
まとめ
JavaScriptにおける数値判定は、歴史的な経緯や暗黙の型変換の影響を受けやすく、非常にデリケートな問題です。
本記事の内容をまとめると、以下のようになります。
- 基本的にはNumber.isFinite()を使用するのが最も安全です。
NaNだけを判定したい場合はNumber.isNaN()を使いましょう。- 整数かどうかのチェックには
Number.isInteger()、大きな整数の精度まで考慮するならNumber.isSafeInteger()が有効です。 - 入力フォームなどの文字列を扱う場合は、
parseFloatや正規表現を組み合わせて、用途に合ったカスタム関数を用意しましょう。 BigIntを扱う場合は、typeofによる型の識別が必要になります。
これら最新の判別テクニックを正しく使い分けることで、バグの少ない、メンテナンス性の高いコードを実現できます。
「数値のように見える値」に惑わされず、プログラムが期待する正確なデータ型と内容を常に保証するよう意識してみてください。
