JavaScript Tips | 数値・計算:NaNを安全に判定する

JavaScript JavaScript
スポンサーリンク
スポンサーリンク
  1. 業務・実務で使える JavaScript ユーティリティ:NaNを安全に判定する方法
  2. まず整理する:NaNとは何か
    1. NaNは「数値として成立しない結果」を表す特殊な値
    2. NaNは number 型である
  3. ステップ1:なぜ NaN === NaN で判定できないのか理解する
    1. 逆に、この性質を利用した判定もある
  4. ステップ2:Number.isNaN() を使う
    1. NaNそのものだけを厳密に判定する
    2. グローバルの isNaN() との違い
  5. ステップ3:判定したい対象を分けて考える
    1. 変換後の結果を検査する例
    2. 入力文字列が数値として不正かを確認する例
  6. ステップ4:NaNだけでなく Infinity も一緒に考える
  7. ステップ5:どの段階で判定するかを決める
  8. 業務で使える基本テンプレート その1:NaNそのものを厳密に判定する
  9. 業務で使える基本テンプレート その2:数値変換の失敗を判定する
  10. 業務で使える完成版テンプレート:安全に数値へ変換する
  11. 初心者が間違えやすいポイントを深掘りする
    1. isNaN() は「NaNそのもの」を見る関数ではない
    2. NaN チェックだけでは通常の数値を保証できない
    3. 空文字や空白だけは Number() で 0 になる
    4. parseInt() や parseFloat() の結果で NaN 判定すると、部分一致を見逃す
  12. 用途別の判定例
    1. ページ番号:1以上の整数であることを確認する
    2. 金額:0以上の有限数であることを確認する
    3. 除算結果のチェック:計算後に NaN を検査する
  13. テストで境界条件を確認する
  14. まとめ

業務・実務で使える JavaScript ユーティリティ:NaNを安全に判定する方法

JavaScriptで計算や数値変換をしていると、ある日突然 NaN が出てきて困ることがあります。フォーム入力の計算、CSVの読み込み、APIレスポンスの加工、割引率の計算、ページ番号の変換など、実務では珍しくありません。

ただし、NaN を安全に判定するには、最初に知っておくべき重要な特徴があります。NaN は「数値ではない」という意味の名前ですが、JavaScriptの型としては number です。さらに、NaN === NaN が false になるという、初心者がつまずきやすい性質もあります。

この記事では、NaN の意味から始めて、なぜ普通の比較で判定できないのか、どの関数を使うべきか、実務で再利用しやすいユーティリティをどう設計するかを、段階的に説明します。

まず整理する:NaNとは何か

NaNは「数値として成立しない結果」を表す特殊な値

NaN は Not-a-Number の略です。数値として計算しようとしたが、正しい数値結果にならなかったときに現れます。

// 数値に変換できない文字列を Number() へ渡す
console.log(Number("abc")); // NaN

// 数学的に定義できない計算の一部でも NaN が出る
console.log(0 / 0); // NaN
JavaScript

業務では、利用者が入力した値が数値でなかったり、計算の途中で不正な値が混ざったりすると、NaN が発生することがあります。

NaNは number 型である

ここが最初の重要ポイントです。名前に「Not-a-Number」と入っていますが、JavaScriptでは NaN は数値型です。

// NaN の型は number
console.log(typeof NaN); // "number"
JavaScript

そのため、typeof value === "number" だけでは、正常な数値と NaN を区別できません。型判定と、値として妥当かどうかの判定は別に考える必要があります。

ステップ1:なぜ NaN === NaN で判定できないのか理解する

NaN のもう1つの大きな特徴は、自分自身と等しくないことです。

// NaN は、自分自身と比較しても false になる
console.log(NaN === NaN); // false
JavaScript

この仕様のため、次のような判定は使えません。

// この判定は期待どおりに動かない
const value = Number("abc");

console.log(value === NaN); // false
JavaScript

つまり、=== や == で NaN を判定しようとしてはいけません。これは初心者が最初につまずきやすいポイントです。

逆に、この性質を利用した判定もある

NaN だけが自分自身と等しくない、という性質を使って、value !== value で判定する方法もあります。

// NaN だけが自分自身と一致しない
function isNaNBySelfComparison(value) {
  return value !== value;
}

console.log(isNaNBySelfComparison(NaN));  // true
console.log(isNaNBySelfComparison(123));  // false
console.log(isNaNBySelfComparison("abc")); // false
JavaScript

ただし、この書き方は仕組みを知っていないと読みづらいため、実務コードでは通常、専用のAPIを使うほうがわかりやすいです。仕組みの理解としては役立ちますが、チーム開発で標準にする必要はありません。

ステップ2:Number.isNaN() を使う

NaNそのものだけを厳密に判定する

実務でまず覚えたいのは Number.isNaN() です。この関数は、値が本当に NaN のときだけ true を返します。

// NaN だけを厳密に判定する
console.log(Number.isNaN(NaN));         // true
console.log(Number.isNaN(Number("abc"))); // true

console.log(Number.isNaN(123));         // false
console.log(Number.isNaN("abc"));       // false
console.log(Number.isNaN(undefined));   // false
JavaScript

ここで重要なのは、"abc" そのものは NaN ではないということです。文字列は文字列であり、数値変換した結果として NaN になるかどうかは別の話です。

グローバルの isNaN() との違い

似た名前の isNaN() というグローバル関数もありますが、こちらは引数をいったん数値へ変換してから判定します。そのため、初心者向けの実務コードでは意図しない true が出やすいです。

// グローバルの isNaN() は、自動で数値変換してから判定する
console.log(isNaN("abc"));      // true
console.log(isNaN(undefined));  // true
console.log(isNaN({}));         // true

// 空文字や空白だけは 0 として扱われるため false になる
console.log(isNaN(""));         // false
console.log(isNaN("   "));      // false

// Number.isNaN() は NaN そのものだけを見る
console.log(Number.isNaN("abc"));      // false
console.log(Number.isNaN(undefined));  // false
console.log(Number.isNaN({}));         // false
JavaScript

この違いを理解していないと、「文字列かどうか」を見たいのか、「数値変換した結果が壊れているか」を見たいのかが曖昧になります。実務では、特別な理由がない限り Number.isNaN() を優先するほうが読みやすく、安全です。

ステップ3:判定したい対象を分けて考える

NaN 判定の実務で大切なのは、「何を判定したいのか」を先に整理することです。主に次の2パターンがあります。

  • すでに計算した結果や変換後の値が、NaN になっていないか確認したい。
  • 入力文字列を数値へ変換したときに、失敗するか確認したい。

この2つは似ていますが、コードの責務が異なります。

変換後の結果を検査する例

// 変換や計算の後で NaN かを確認する
const value = Number("abc");

if (Number.isNaN(value)) {
  console.log("数値変換に失敗しました");
}
JavaScript

この用途では、Number.isNaN() がそのまま使えます。

入力文字列が数値として不正かを確認する例

// 入力文字列の妥当性を確認する
function isInvalidNumberInput(value) {
  // 文字列以外はこの関数では不正とみなす
  if (typeof value !== "string") {
    return true;
  }

  // 前後の空白を除去する
  const trimmed = value.trim();

  // 空文字は未入力として不正にする
  if (trimmed === "") {
    return true;
  }

  // 数値変換の結果が NaN なら不正
  return Number.isNaN(Number(trimmed));
}

console.log(isInvalidNumberInput("123"));   // false
console.log(isInvalidNumberInput("abc"));   // true
console.log(isInvalidNumberInput("   "));   // true
JavaScript

この例では、「値そのものが NaN か」ではなく、「数値として使える入力か」を判定しています。役割を分けて書くことで、コードの意図が伝わりやすくなります。

ステップ4:NaNだけでなく Infinity も一緒に考える

実務では、NaN だけを弾けば十分とは限りません。Infinity や -Infinity も、通常の金額や件数では不正として扱いたいことが多いです。

// Infinity は NaN ではない
console.log(Number.isNaN(Infinity));   // false
console.log(Number.isNaN(-Infinity));  // false
JavaScript

そのため、「NaNではない」ことを確認するだけでは、通常の数値として安全とは言い切れません。業務では、NaNチェックよりも、有限の数値かどうかを確認する Number.isFinite() のほうが実用的な場面が多いです。

// 有限の通常数だけを true にする
console.log(Number.isFinite(123));        // true
console.log(Number.isFinite(3.14));       // true
console.log(Number.isFinite(NaN));        // false
console.log(Number.isFinite(Infinity));   // false
console.log(Number.isFinite("123"));      // false
JavaScript

つまり、NaN を安全に判定したいという課題の先には、「この値を計算や業務処理へ渡してよいか」という目的があります。その視点で考えると、Number.isNaN() と Number.isFinite() を使い分けることが大切です。

ステップ5:どの段階で判定するかを決める

実務コードでは、NaNを見つけた時点で場当たり的に処理するより、入力から変換までの流れをそろえるほうが保守しやすくなります。考え方としては、次の順番がわかりやすいです。

  • 入力の型を確認する。
  • 必要なら前処理を行う。
  • 数値へ変換する。
  • NaN や Infinity を除外する。
  • 整数、範囲、0以上などの業務ルールを確認する。

この順番を関数としてまとめておくと、画面入力、CSV、APIなど複数の経路で同じルールを使いやすくなります。

業務で使える基本テンプレート その1:NaNそのものを厳密に判定する

まずは、値が本当に NaN かだけを確認する、最小の関数です。

/**
 * 値が NaN かどうかを厳密に判定します。
 *
 * @param {unknown} value 判定対象
 * @returns {boolean} NaN なら true
 */
function isActualNaN(value) {
  // Number.isNaN() は NaN そのものだけを true にする
  return Number.isNaN(value);
}

console.log(isActualNaN(NaN));         // true
console.log(isActualNaN(Number("abc"))); // true
console.log(isActualNaN("abc"));       // false
console.log(isActualNaN(undefined));   // false
JavaScript

この関数は、計算結果や変換後の値の検査に使いやすいです。一方で、入力文字列を直接渡して判定する用途には向いていません。

業務で使える基本テンプレート その2:数値変換の失敗を判定する

次に、フォーム入力などの文字列を受け取り、数値変換に失敗したかを判定する関数です。

/**
 * 文字列入力が数値変換に失敗するかを判定します。
 *
 * 仕様:
 * - 文字列以外は不正とみなします。
 * - 前後の空白を除去します。
 * - 空文字は未入力として不正にします。
 * - Number() の結果が NaN なら true を返します。
 *
 * @param {unknown} value 判定対象
 * @returns {boolean} 数値変換に失敗するなら true
 */
function isNaNInput(value) {
  // 文字列以外は、この関数では不正とする
  if (typeof value !== "string") {
    return true;
  }

  // 前後の空白を除去する
  const trimmed = value.trim();

  // 空文字は変換前に不正とする
  if (trimmed === "") {
    return true;
  }

  // 数値変換の結果が NaN なら失敗
  return Number.isNaN(Number(trimmed));
}

console.log(isNaNInput("123"));    // false
console.log(isNaNInput("3.14"));   // false
console.log(isNaNInput("abc"));    // true
console.log(isNaNInput(""));       // true
console.log(isNaNInput("   "));    // true
JavaScript

ただし、この関数は「NaNになるか」だけを見ています。Infinity は NaN ではないため、必要に応じてさらに条件を追加する必要があります。

業務で使える完成版テンプレート:安全に数値へ変換する

実務では、最終的に知りたいことは「NaNかどうか」そのものより、「この値を通常の数値として使ってよいか」であることが多いです。そこで、変換と判定をまとめた関数を用意します。

/**
 * 入力値を安全に有限数へ変換します。
 *
 * 仕様:
 * - 数値型はそのまま確認します。
 * - 文字列は trim() 後に Number() で変換します。
 * - 空文字、空白だけの文字列は不正です。
 * - NaN、Infinity、-Infinity は不正です。
 * - 変換できたら number を返し、失敗したら null を返します。
 *
 * @param {unknown} value 入力値
 * @returns {number | null} 正常な有限数なら number、失敗なら null
 */
function toFiniteNumber(value) {
  // すでに数値型なら、そのまま有限か確認する
  if (typeof value === "number") {
    return Number.isFinite(value) ? value : null;
  }

  // 文字列なら前処理してから変換する
  if (typeof value === "string") {
    const trimmed = value.trim();

    // 空文字は未入力として扱う
    if (trimmed === "") {
      return null;
    }

    const converted = Number(trimmed);

    // NaN や Infinity を除外する
    return Number.isFinite(converted) ? converted : null;
  }

  // それ以外の型は不正とする
  return null;
}

/**
 * 値が安全に使える数値かどうかを判定します。
 *
 * @param {unknown} value 入力値
 * @returns {boolean} 有限数として使えるなら true
 */
function isUsableNumber(value) {
  return toFiniteNumber(value) !== null;
}

// 利用例
console.log(toFiniteNumber("123"));       // 123
console.log(toFiniteNumber(" 3.14 "));    // 3.14
console.log(toFiniteNumber("abc"));       // null
console.log(toFiniteNumber(""));          // null
console.log(toFiniteNumber(NaN));         // null
console.log(toFiniteNumber(Infinity));    // null

console.log(isUsableNumber("123"));       // true
console.log(isUsableNumber("abc"));       // false
console.log(isUsableNumber(Infinity));    // false
JavaScript

この関数の利点は、NaN だけでなく、空文字や Infinity もまとめて扱えることです。業務ルールとして「使える数値」を定義しやすくなります。

初心者が間違えやすいポイントを深掘りする

isNaN() は「NaNそのもの」を見る関数ではない

グローバルの isNaN() は、名前だけ見ると便利そうですが、実際には「数値へ変換した結果が NaN か」を見ています。そのため、入力検証では予想外の結果になります。

// 自動変換の影響で結果がわかりにくい
console.log(isNaN("abc"));    // true
console.log(isNaN("123"));    // false
console.log(isNaN(""));       // false
console.log(isNaN("   "));    // false
console.log(isNaN(true));     // false
console.log(isNaN(undefined)); // true
JavaScript

たとえば空文字が false になるのは、数値変換で 0 になるためです。未入力を見逃しやすくなるため、実務では注意が必要です。

NaN チェックだけでは通常の数値を保証できない

Number.isNaN(value) が false でも、その値が通常の数値とは限りません。たとえば Infinity は NaN ではありません。

// NaN ではなくても、業務で使いたくない値がある
console.log(Number.isNaN(Infinity));   // false
console.log(Number.isNaN(-Infinity));  // false
JavaScript

金額、個数、割合などを扱うなら、NaNかどうかよりも、有限の通常数かどうかで考えるほうが安全です。

空文字や空白だけは Number() で 0 になる

数値変換の挙動で、特に実務事故につながりやすいのが空文字です。

// 空文字や空白だけは 0 になる
console.log(Number(""));    // 0
console.log(Number("   ")); // 0
JavaScript

未入力なのに 0 として集計されると、売上や件数のデータがおかしくなります。そのため、入力文字列を扱うときは、まず trim() して空かどうかを確認する癖を付けるとよいです。

parseInt() や parseFloat() の結果で NaN 判定すると、部分一致を見逃す

parseInt() や parseFloat() は、先頭から読める部分だけを数値として扱います。そのため、入力全体が正しい数値かを確認したい用途には向きません。

// 途中まで読めれば数値扱いされる
console.log(parseInt("123abc", 10)); // 123
console.log(parseFloat("3.14xyz"));  // 3.14
JavaScript

この結果に対して Number.isNaN() を使っても、入力全体の不正は見つけられません。厳密な入力検証では、Number() と空文字チェックを組み合わせるほうが扱いやすくなります。

用途別の判定例

ページ番号:1以上の整数であることを確認する

/**
 * ページ番号として妥当か判定します。
 *
 * @param {unknown} value 入力値
 * @returns {boolean} 1以上の整数なら true
 */
function isPageNumber(value) {
  const number = toFiniteNumber(value);

  if (number === null) {
    return false;
  }

  return Number.isInteger(number) && number >= 1;
}

console.log(isPageNumber("1"));    // true
console.log(isPageNumber("0"));    // false
console.log(isPageNumber("abc"));  // false
console.log(isPageNumber("1.5"));  // false
JavaScript

金額:0以上の有限数であることを確認する

/**
 * 金額として妥当か判定します。
 *
 * @param {unknown} value 入力値
 * @returns {boolean} 0以上の有限数なら true
 */
function isPrice(value) {
  const number = toFiniteNumber(value);

  if (number === null) {
    return false;
  }

  return number >= 0;
}

console.log(isPrice("1000"));    // true
console.log(isPrice("3.14"));    // true
console.log(isPrice("-1"));      // false
console.log(isPrice("abc"));     // false
JavaScript

除算結果のチェック:計算後に NaN を検査する

/**
 * 2つの値を割り算し、安全な数値で返します。
 *
 * @param {unknown} a 割られる値
 * @param {unknown} b 割る値
 * @returns {number | null} 正常なら結果、失敗なら null
 */
function safeDivide(a, b) {
  const left = toFiniteNumber(a);
  const right = toFiniteNumber(b);

  // 入力の段階で不正な値を除外する
  if (left === null || right === null) {
    return null;
  }

  const result = left / right;

  // 0除算や想定外の値を除外する
  return Number.isFinite(result) ? result : null;
}

console.log(safeDivide("10", "2"));  // 5
console.log(safeDivide("10", "0"));  // null
console.log(safeDivide("abc", "2")); // null
JavaScript

このように、入力変換と計算結果の検査を分けておくと、どこで不正値が混ざったかを追いやすくなります。

テストで境界条件を確認する

NaN判定は短いコードでも、境界条件が多いです。通常の数値だけでなく、空文字、空白、Infinity、文字列混在なども確認します。

// toFiniteNumber() のテスト例
const testCases = [
  [123, 123],
  [3.14, 3.14],
  [NaN, null],
  [Infinity, null],
  [-Infinity, null],
  ["123", 123],
  [" 3.14 ", 3.14],
  ["abc", null],
  ["", null],
  ["   ", null],
  ["123abc", null],
  [null, null],
  [undefined, null],
  [true, null],
  [false, null],
  [{}, null]
];

// 期待値と実際の値を比較する
for (const [input, expected] of testCases) {
  const actual = toFiniteNumber(input);

  console.log({
    input,
    expected,
    actual,
    pass: Object.is(actual, expected)
  });
}
JavaScript

また、Number.isNaN() と isNaN() の違いを理解するために、比較テストを書いておくのも有効です。

// 2つの関数の違いを確認する
const values = [NaN, "abc", "", "   ", undefined, 123];

for (const value of values) {
  console.log({
    value,
    globalIsNaN: isNaN(value),
    numberIsNaN: Number.isNaN(value)
  });
}
JavaScript

このようなテストは、チーム内で「どの関数を標準にするか」を共有するときにも役立ちます。

まとめ

JavaScriptでNaNを安全に判定するには、まず NaN の性質を正しく理解することが大切です。NaN は数値型であり、通常の比較演算では判定できません。

  • NaN === NaN は false になるため、比較演算で判定しません。
  • NaN そのものを厳密に判定するなら、Number.isNaN() を使います。
  • グローバルの isNaN() は自動変換を行うため、入力検証では注意が必要です。
  • 実務では、NaN だけでなく Infinity も除外するため、Number.isFinite() を使う場面が多くなります。
  • 空文字や空白は Number() で 0 になるため、変換前のチェックが重要です。
  • 入力の正規化、数値変換、業務ルールの判定を分けると保守しやすくなります。

実務で本当に必要なのは、「NaNを見つけること」そのものではなく、「不正な入力や壊れた計算結果を業務処理へ流さないこと」です。用途を限定した小さな関数と、明確な入力ルールを組み合わせることで、安心して再利用できるコードになります。

タイトルとURLをコピーしました