JavaScript で数値を扱う際に、意外と頻繁に登場するのが NaN (Not-a-Number) という特殊な値です。NaN は「数値型ではあるが有効な数値ではない」ことを表しており、計算処理の途中で不正な演算が行われたときに発生します。たとえば、文字列を数値に変換できなかった場合や、0 / 0
のような数学的に定義できない演算を行った場合に NaN
が返されます。
この NaN の存在は非常に厄介です。なぜなら、NaN は どんな値とも等しくない(NaN === NaN
ですら false
になる) という性質を持っているため、通常の比較演算子で判定できないからです。そのため、JavaScript には isNaN
という専用の判定関数が用意されています。
しかし、この isNaN
は一見便利に見える反面、型変換を暗黙に行うために意図しない結果を返すことがある という落とし穴があります。特に実務では、ユーザー入力や API レスポンスといった外部データを扱う際に「これは本当に計算に使える数値なのか?」を確実に判定したい場面が多く、この挙動がバグや不具合の温床になりかねません。
そこで登場するのが Number.isNaN
です。Number.isNaN
は isNaN
の欠点を解消し、厳密に「値が NaN であるか」を判定します。本記事では isNaN
と Number.isNaN
の挙動の違いを具体例とともに解説し、さらに実務で役立つ「数値妥当性チェック関数」の実装例、そして TypeScript を用いた型安全なアプローチまで紹介します。
isNaN の挙動
isNaN
はグローバル関数であり、引数を一度数値へと強制的に変換してから、その結果が NaN かどうかを判定する という仕組みになっています。
この「暗黙の型変換」が最大の特徴であり、同時に混乱の原因でもあります。
たとえば次のように文字列 "foo"
を渡した場合、JavaScript はまずこれを数値に変換しようと試みます。しかし "foo"
は数値に変換できないため NaN
となり、結果として isNaN("foo")
は true
を返します。
逆に "123"
のように数値として解釈可能な文字列は、内部的に 123
に変換されます。そのため isNaN("123")
は false
となり、一見すると「有効な数値である」と判断されてしまいます。
さらに undefined
を渡した場合も注意が必要です。undefined
は数値に変換されると NaN
になるため、isNaN(undefined)
は true
となります。これも直感的ではなく、型安全性の観点からすると危険な挙動です。
console.log(isNaN("foo")); // true ("foo" → NaN)
console.log(isNaN("123")); // false ("123" → 123)
console.log(isNaN(undefined)); // true (undefined → NaN)
console.log(isNaN(NaN)); // true
つまり isNaN
は 「数値に変換できないものを検出する」関数 とも言えます。
一方で「そもそも入力が数値型かどうか」を判定したい場合には役立ちません。"123"
のような文字列を「有効な数値」とみなして通してしまうため、実務では想定外のデータが後続処理に紛れ込むリスクが高くなります。
このため isNaN
を無条件に利用すると、入力チェックやバリデーションにおいて「思っていたのと違う」挙動になりやすく、バグの原因となりやすいのです。
Number.isNaN の挙動
Number.isNaN
は ES6 で追加されたメソッドで、isNaN
の問題点を解消するために導入されました。最大の特徴は 引数の型変換を一切行わない ことです。つまり、渡された値が「厳密に NaN であるかどうか」だけを判定します。
この挙動により、前述の isNaN
で見られたような「文字列や undefined
が暗黙に数値変換されてしまう」という予期せぬ動きは発生しません。
console.log(Number.isNaN("foo")); // false
console.log(Number.isNaN("123")); // false
console.log(Number.isNaN(undefined)); // false
console.log(Number.isNaN(NaN)); // true
たとえば "foo"
を渡しても NaN 判定は行われず、単に「文字列なので NaN ではない」と判断されます。"123"
も同様で、「数値に変換できるかどうか」は考慮せず「これは数値型ではない」として false を返します。また undefined
の場合も型変換は行われないため、結果は false
となります。
一方で、本当に NaN
が渡された場合だけは確実に true
が返ってきます。これにより 「値が NaN
そのものかどうか」 を判定するための純粋で信頼できる手段を得られるわけです。
この挙動は、前述の isNaN
と比べると非常に直感的でわかりやすいです。isNaN
が「数値に変換できるかどうか」という観点で判定していたのに対し、Number.isNaN
は「数値型の中で NaN
という特別な値であるかどうか」だけに絞り込んでいるためです。
このため後述の比較章でも触れるように、バリデーションや入力チェックで意図しない値を通してしまうリスクを避けるためには、Number.isNaN
を使うのが圧倒的に安全 という結論につながります。
違いのまとめ
ここまで見てきたように、isNaN
と Number.isNaN
は一見似ているものの、挙動は大きく異なります。表で整理すると次のようになります。
入力値 | isNaN | Number.isNaN |
---|---|---|
“foo” | true | false |
“123” | false | false |
undefined | true | false |
NaN | true | true |
123 | false | false |
“foo” のケース
isNaN("foo")
は "foo"
を数値に変換しようとし、結果的に NaN
となるため true
を返します。これは「数値に変換できない」という観点から見れば正しいですが、文字列がそのまま通ってしまうのは実務的には危険です。
一方 Number.isNaN("foo")
は変換を行わないため、単なる文字列として扱われ、false
が返ります。こちらの方が「これは数値ではない」という意図に近い結果です。
“123” のケース
"123"
のように数値に変換可能な文字列については、isNaN
も Number.isNaN
もどちらも false を返します。つまり 表面上の判定結果は同じ です。
ただし、その理由は異なります。
isNaN("123")
の場合は、内部で"123"
を数値に変換し、結果が123
になるため「NaN
ではない」と判断しています。Number.isNaN("123")
の場合は、そもそも"123"
は数値型ではないため「NaN
と一致しない」という理由でfalse
を返します。
このように両者は同じ結果を返しますが、「変換したうえで判定しているのか」「そのままの型で判定しているのか」 というロジックの違いが存在します。
undefined のケース
isNaN
は引数を数値に変換してから判定を行うため、undefined
や null
に対しても意外な結果を返します。
undefined
は数値変換されるとNaN
になるため、isNaN(undefined)
はtrue
を返します。null
は数値変換されると 0 になるため、isNaN(null)
はfalse
を返します。
一方で Number.isNaN
は型変換を行わないため、両方とも「NaN
そのものではない」と判断し、false
を返します。
console.log(isNaN(undefined)); // true (undefined → NaN)
console.log(Number.isNaN(undefined)); // false
console.log(isNaN(null)); // false (null → 0)
console.log(Number.isNaN(null)); // false
このように、isNaN
は undefined
を NaN
と誤認し、逆に null
を数値 0
として扱ってしまうため直感的ではありません。対して Number.isNaN
はどちらの値も単なる「NaN
ではない別の型」として処理するため、より安全で一貫性のある挙動を示します。
NaN と数値そのもののケース
最後に、実際に NaN
そのものや通常の数値を渡した場合の挙動です。ここについては isNaN
と Number.isNaN
の両者ともに同じ結果を返します。
console.log(isNaN(NaN)); // true
console.log(Number.isNaN(NaN)); // true
console.log(isNaN(123)); // false
console.log(Number.isNaN(123)); // false
NaN
を渡した場合は、どちらも正しくtrue
を返します。- 有効な数値(例:
123
)を渡した場合は、どちらもfalse
を返します。
一見すると「両者に差はない」と思えますが、この挙動は NaN
が非常に特殊な値であること を改めて示しています。
通常の比較演算子では NaN を検出できません。例えば NaN === NaN
は false
になります。これは ECMAScript の仕様で、NaN は「どんな値とも等しくない」という性質を持っているためです。
console.log(NaN === NaN); // false
そのため、NaN
を判定するには専用の関数を使うしかありません。このときに isNaN
と Number.isNaN
はどちらも正しく動作しますが、問題は「NaN 以外の入力をどう扱うか」です。
前述のとおり isNaN
は暗黙の型変換を行うため、undefined
や "foo"
といった値に対しても true
を返してしまう可能性があります。つまり「本当に NaN かどうか」を見たい場合には誤判定が起こりやすいのです。
一方 Number.isNaN
は「値が数値型かつ NaN
である場合のみ true
」を返すため、NaN を確実に検出でき、かつ余計な誤判定をしません。
その意味で、NaN
そのものを検出するための信頼できる関数は Number.isNaN
であると結論づけられます。
Infinity と -Infinity のケース
では、数値型におけるもう一つの特殊な値である Infinity
と -Infinity
を渡した場合はどうでしょうか。
console.log(isNaN(Infinity)); // false
console.log(Number.isNaN(Infinity)); // false
console.log(isNaN(-Infinity)); // false
console.log(Number.isNaN(-Infinity)); // false
どちらの関数も Infinity
や -Infinity
に対しては false を返します。
これは「無限大は特殊な値ではあるものの、NaN
ではない」という扱いが仕様として定められているためです。
実際、JavaScript では以下のような挙動をします。
console.log(1 / 0); // Infinity
console.log(-1 / 0); // -Infinity
console.log(0 / 0); // NaN
0
で割った場合でも分子が非ゼロならInfinity
または-Infinity
になります。- 逆に
0 / 0
のように数学的に定義できない演算を行った場合にのみNaN
が返ります。
したがって、Infinity
系は「有効な数値ではあるが有限ではない」、NaN
は「そもそも数値として成立していない」 という違いがあります。
両者ともに isNaN
/ Number.isNaN
での判定結果は一致しますが、意味合いは大きく異なるため、Infinity
を検出したい場合には別の関数(例えば Number.isFinite
)を使うべきです。
まとめると、
isNaN
… 引数を数値に変換してから判定するため、外部入力のバリデーションには不向き。Number.isNaN
… 値が厳密に NaN であるかどうかだけを判定するため、直感的で安全。
したがって、実務における入力チェックや計算前の検証では、Number.isNaN
を優先して用いるべきであることが明確になります。
厳密に「正しい数値」かどうかを判定する関数
ここまでで見たように、isNaN
や Number.isNaN
は「NaN かどうか」を調べる関数にすぎません。しかし、実務で必要なのは単に NaN を弾くだけではなく、「この値は後続の計算処理に使っても問題ないかどうか」 を判定することです。
例えば次のようなケースを考えてみます。
- ユーザーがフォームに入力した値をサーバー側で計算に利用する
- API から取得したレスポンスに数値が含まれており、それを集計に使う
- センサーやログから取得したデータをグラフに可視化する
これらの場面では、単に NaN を排除するだけでは不十分です。null
や undefined
、あるいは文字列が紛れ込んでいたとしても、それらを「計算できる値」として扱ってしまうとバグや異常な挙動の原因になります。
そのため、「厳密に数値型である」ことと「NaN ではない」ことを両方確認するチェック関数 が必要になります。
数値型かつ NaN ではない値を判定する関数
まずは「入力が数値型であり、かつ NaN ではないか」をチェックする関数です。
function isUsableNumber(value) {
return typeof value === "number" && !Number.isNaN(value);
}
// 利用例
console.log(isUsableNumber(123)); // true
console.log(isUsableNumber(NaN)); // false
console.log(isUsableNumber("123")); // false
console.log(isUsableNumber(null)); // false
console.log(isUsableNumber(undefined)); // false
この関数を使うことで、文字列や null
が紛れ込んでも誤って有効な数値と判断されない ため、後続処理を安全に進められます。特に業務システムや数値計算系の処理では必須のガードになります。
文字列も数値として許容したい場合
一方で、ユースケースによっては "123"
のような文字列入力を数値に変換して受け入れたい場合もあります。たとえば、フォーム入力やCSVファイルの読み込みなど、外部データは文字列で渡ってくることが多いからです。
その場合は、数値変換したうえで判定を行う関数を用意すると便利です。
function isConvertibleToNumber(value) {
const num = Number(value);
return !Number.isNaN(num);
}
// 利用例
console.log(isConvertibleToNumber("123")); // true (→ 123 に変換可能)
console.log(isConvertibleToNumber("foo")); // false (→ NaN)
console.log(isConvertibleToNumber(123)); // true
console.log(isConvertibleToNumber(NaN)); // false
この関数を使えば、外部から文字列として値を受け取った場合でも安全に「数値として利用できるかどうか」を確認できます。
Infinity の扱いに注意
さらに厳密に「正しい数値」を定義したい場合には、Infinity
や -Infinity
も考慮する必要があります。これらは数値型でありながら有限ではないため、計算結果を大きく狂わせる可能性があります。
その場合は Number.isFinite
を組み合わせてチェックします。
function isFiniteNumber(value) {
return typeof value === "number" && Number.isFinite(value);
}
// 利用例
console.log(isFiniteNumber(123)); // true
console.log(isFiniteNumber(NaN)); // false
console.log(isFiniteNumber(Infinity)); // false
console.log(isFiniteNumber(-Infinity)); // false
これで「数値型かつ有限な値」であることを保証でき、最も厳密な意味で「正しい数値」を判定できます。
まとめ
Number.isNaN
は「NaN かどうか」を判定するだけ。- 実務では 数値型であること、場合によっては 有限であること を保証する関数が必要になる。
- 入力の性質(数値型のみを想定するのか、文字列入力も許容するのか)に応じて、チェック関数を実装し使い分けるのがベストプラクティス。
TypeScript での型安全なアプローチ
JavaScript の場合、isNaN
や Number.isNaN
のような関数に頼らないと「その値が計算に使える正しい数値かどうか」を判断できません。なぜなら、JavaScript は動的型付け言語であり、関数の引数に文字列や null
が渡ってきてもコンパイル時にエラーにならないからです。
そのため、実務で入力チェックを厳密に行いたい場合には、自前で判定関数を作る必要があります。
一方で TypeScript を使えば、コンパイル時に型の安全性を保証できるため、実行時のチェックを最小限に抑えることが可能です。これにより「文字列が紛れ込んでいた」などの典型的なバグを、コードが実行される前に防げます。
数値型だけを受け入れる関数
TypeScript では関数の引数に型を指定できるため、number
型以外の値が渡された時点でコンパイルエラーとなります。
function isUsableNumber(value: number): boolean {
return !Number.isNaN(value);
}
// 利用例
console.log(isUsableNumber(123)); // true
console.log(isUsableNumber(NaN)); // false
// 以下はすべてコンパイルエラー
// console.log(isUsableNumber("123"));
// console.log(isUsableNumber(null));
// console.log(isUsableNumber(undefined));
JavaScript では実行してみないとエラーが出ないケースも、TypeScript ではコンパイル時に検出できるため、「想定外の型が混入する」リスクを大幅に減らすことができます。
外部入力を扱う場合
ただし、ユーザー入力や API からのレスポンスなど、外部データはすべて unknown
や string
型として扱われることが多いため、そのまま数値として信頼することはできません。
この場合は「外部入力を number
に変換する処理」と「変換結果の妥当性チェック」を組み合わせるのが現実的です。
function parseNumber(value: unknown): number | null {
const num = Number(value);
return Number.isNaN(num) ? null : num;
}
// 利用例
console.log(parseNumber("123")); // 123
console.log(parseNumber("foo")); // null
console.log(parseNumber(42)); // 42
この関数を通すことで、外部入力が不正な場合は null
を返すため、安全に扱えます。
つまり 外部データを受け取る段階で型を狭め、アプリケーション内部では常に number
型として扱う、という二段構えが理想的です。
Infinity の扱い
さらに厳密に「正しい数値」を保証したい場合には、Infinity
や -Infinity
を排除することも考えられます。TypeScript ではこれも関数化して型安全に扱えます。
function isFiniteNumber(value: number): boolean {
return Number.isFinite(value);
}
console.log(isFiniteNumber(123)); // true
console.log(isFiniteNumber(NaN)); // false
console.log(isFiniteNumber(Infinity)); // false
console.log(isFiniteNumber(-Infinity)); // false
このように TypeScript では 引数の型を number
に制約した上で、有限数であることを保証する 関数を定義できます。これにより、アプリケーション内部の計算処理に不正な値が入り込むのを防げます。
実務での利点
- JavaScript 単体 → 「型が曖昧なので実行時チェックが必須」
- TypeScript 利用時 → 「コンパイル時に型を保証し、実行時チェックは外部入力の変換処理に限定できる」
このように、TypeScript を導入すると「型安全性」と「実行時のバリデーション」をうまく分担でき、堅牢なシステム設計につながります。
おわりに
ここまで isNaN
と Number.isNaN
の違いを整理し、さらに実務で役立つチェック関数や TypeScript での型安全なアプローチについて解説しました。
まず理解すべきは、isNaN
は一見便利な関数ではあるものの、暗黙の型変換を行うため外部入力の妥当性確認には適さないということです。undefined
を NaN と判定したり、null
を 0
とみなしたりする挙動は直感に反し、バグの温床になりかねません。
それに対して Number.isNaN
は「値が厳密に NaN であるかどうか」だけを判定するため、余計な誤判定がなく、より直感的で安全な結果を返すことができます。単純に「NaN かどうか」を調べたいのであれば、常に Number.isNaN
を選ぶべきです。
しかし、実務で本当に必要なのは「NaN 判定」そのものではなく、その値が計算に使える正しい数値であるかどうかの判定です。そのためには typeof
や Number.isFinite
を組み合わせたチェック関数を定義し、用途に応じて使い分ける必要があります。たとえば「数値型かつ NaN ではないか」「数値に変換可能か」「有限な数値か」など、要件に応じた粒度で判定を行うのが望ましいでしょう。
さらに TypeScript を導入すれば、これらのチェックの多くはコンパイル時に解決できます。型による制約で "123"
や null
が紛れ込むことを防ぎ、実行時のチェックは外部入力の変換に限定する設計が可能です。結果として、JavaScript の弱点を補いながら、より堅牢で信頼性の高いコード を書けるようになります。
結論として、次のように整理できます。
- NaN 判定には
Number.isNaN
を使うべき。 - 入力バリデーションでは、用途に応じて「数値型か」「有限か」などをチェックする専用関数を用意する。
- TypeScript を使う場合は、型システムで保証できる部分と実行時の変換・チェックを分離し、シンプルな設計を心がける。
これらを徹底することで、「思わぬ型変換により不正な値が計算に紛れ込む」といった典型的なバグを防ぎ、安心して数値を扱えるコード基盤を構築できます。