JavaScript Tips | 数値・計算:数値かどうか判定する

JavaScript JavaScript
スポンサーリンク
スポンサーリンク

業務・実務で使える JavaScript ユーティリティ:数値かどうか判定する方法

JavaScriptで「数値かどうか」を判定したい場面は非常に多いです。フォーム入力の金額、CSVから読み込んだ数量、APIで受け取った価格、ページ番号、在庫数、割引率など、業務システムでは数値の扱いが頻繁に登場します。

ただし、ここで最初に理解しておきたいのは、「数値かどうか」は1通りではないということです。たとえば、123 のようなJavaScriptの数値型だけを許可したいのか、"123" のような文字列も「数値として読める値」として許可したいのかで、判定方法は変わります。

さらに、NaN、Infinity、空文字、空白だけの文字列、16進数表記、指数表記など、初心者が見落としやすいケースもあります。この記事では、まず「何を数値と呼ぶのか」を整理し、そのうえで実務向けの判定関数を段階的に作っていきます。

最初に整理する:「数値かどうか」には3種類ある

実務で混乱しやすい原因は、「数値」という言葉が、複数の意味で使われることです。まずは、次の3つを分けて考えると整理しやすくなります。

  • JavaScriptの値として、すでに数値型であるか。
  • 文字列だが、数値として読み取れる形をしているか。
  • 金額、数量、整数、0以上など、業務ルールを満たす数値か。

たとえば、123 は数値型です。"123" は数値型ではありませんが、数値として解釈できる文字列です。さらに -10 は数値ですが、「個数は0以上」という業務ルールには合いません。

つまり、型の判定、変換できるかの判定、業務条件の判定を混同しないことが重要です。

ステップ1:まずはJavaScriptの数値型かどうか判定する

typeof で数値型かを確認する

JavaScriptで値の型を調べる基本は typeof です。数値型なら "number" が返ります。

// 値の型を確認する基本例
console.log(typeof 123);      // "number"
console.log(typeof 3.14);     // "number"
console.log(typeof "123");    // "string"
console.log(typeof true);     // "boolean"
console.log(typeof null);     // "object"
JavaScript

これだけ見ると、typeof value === "number" で十分に見えます。しかし、ここには大きな注意点があります。

NaN と Infinity も number 型である

NaN は「Not-a-Number」という名前ですが、JavaScriptでは数値型です。また、Infinity と -Infinity も数値型です。

// どれも typeof は "number" になる
console.log(typeof NaN);       // "number"
console.log(typeof Infinity);  // "number"
console.log(typeof -Infinity); // "number"
JavaScript

つまり、typeof value === "number" だけでは、「有限の通常の数値かどうか」は判定できません。業務で扱う金額や件数などでは、NaN や Infinity を許可しないことがほとんどです。

ステップ2:有限の数値かどうかを判定する

Number.isFinite() を使う

業務で最もよく使いやすい基本判定は、Number.isFinite() です。これは、値が数値型であり、かつ NaN や Infinity ではない場合にだけ true を返します。

// 有限の数値だけを 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
console.log(Number.isFinite(null));       // false
JavaScript

この関数の良いところは、"123" のような文字列を勝手に数値とみなさないことです。型の厳密さを保ちたい業務コードでは、まずこれを基本に考えると安全です。

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

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

// グローバルの isFinite() は自動変換する
console.log(isFinite("123")); // true
console.log(isFinite(""));    // true
console.log(isFinite(" "));   // true
console.log(isFinite(null));  // true
<br>
// Number.isFinite() は型も厳密に確認する
console.log(Number.isFinite("123")); // false
console.log(Number.isFinite(""));    // false
console.log(Number.isFinite(" "));   // false
console.log(Number.isFinite(null));  // false
JavaScript

isFinite("") が true になるのは、空文字が数値変換で 0 になるためです。これはフォーム入力の検証などでは非常に危険です。実務では、特別な理由がない限り Number.isFinite() を優先するほうがわかりやすくなります。

ステップ3:文字列として数値を受け取りたい場合を考える

フォーム入力やCSVでは、値が文字列で渡されることがよくあります。そのため、"123" や "3.14" を「数値として使える入力」として扱いたい場面があります。

ただし、ここで大切なのは、「文字列をそのまま数値型と判定する」のではなく、「数値へ安全に変換できるか」を確認するという考え方です。

Number() で変換を試す

文字列を数値へ変換する代表的な方法の1つが Number() です。

// 文字列を数値へ変換する
console.log(Number("123"));    // 123
console.log(Number("3.14"));   // 3.14
console.log(Number("-10"));    // -10
console.log(Number("1e3"));    // 1000
console.log(Number(""));       // 0
console.log(Number("   "));    // 0
console.log(Number("abc"));    // NaN
JavaScript

ここで特に注意したいのは、空文字や空白だけの文字列が 0 になることです。これは業務入力の検証では、ほとんどの場合で望ましくありません。

空文字・空白を先に除外する

そのため、文字列を数値として受け入れる関数では、先に trim() で前後の空白を除去し、空になったら不正とするのが実務的です。

// 文字列が数値として読めるかを簡単に判定する
function isNumericString(value) {
  // 文字列以外は受け付けない
  if (typeof value !== "string") {
    return false;
  }

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

  // 空文字は数値として扱わない
  if (trimmed === "") {
    return false;
  }

  // 数値へ変換し、有限か確認する
  return Number.isFinite(Number(trimmed));
}

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

この形にすると、フォーム入力やCSVの値を扱うときに、空欄と数値入力を区別しやすくなります。

ステップ4:parseInt() や parseFloat() を判定用途に使いすぎない

初心者がよく使うのが parseInt() や parseFloat() ですが、これらは「先頭から読めるところまで読む」関数です。そのため、厳密な入力チェックには向かない場合があります。

// 途中まで読めれば数値として返してしまう
console.log(parseInt("123abc", 10));   // 123
console.log(parseFloat("3.14xyz"));    // 3.14
console.log(parseInt("08", 10));       // 8
JavaScript

たとえば、数量入力として "123abc" を不正にしたいなら、parseInt() の結果だけで判断してはいけません。入力全体が数値として妥当かを見たいなら、まずは Number() と明示的な条件分岐で考えるほうが安全です。

parseInt() や parseFloat() は、「先頭の数値部分だけ抜き出したい」という用途では有効ですが、「数値として正しい入力か」の判定とは役割が異なります。

ステップ5:何を許可するかを業務ルールとして決める

ここまでで、次の2つの基本パターンが見えてきます。

  • すでに数値型で、通常の有限数だけを許可したい。
  • 文字列も受け付け、数値へ変換できるなら許可したい。

さらに実務では、次のような条件が追加されることが多いです。

  • 整数だけを許可する。
  • 0以上だけを許可する。
  • 1以上100以下の範囲だけを許可する。
  • 小数点以下2桁までを許可する。

つまり、数値判定の関数は1個だけで万能にしようとするより、基本の判定関数と、用途別の条件チェックを分けるほうが保守しやすくなります。

業務で使える基本テンプレート その1:有限の数値型か判定する

まずは、JavaScriptの値として、通常の有限数だけを許可する関数です。内部計算やAPIレスポンスの検証などで使いやすい形です。

/**
 * 値が有限の数値型かを判定します。
 *
 * @param {unknown} value 判定対象の値
 * @returns {boolean} 有限の数値型なら true
 */
function isFiniteNumber(value) {
  // 数値型であり、NaN や Infinity ではない場合だけ true
  return Number.isFinite(value);
}

console.log(isFiniteNumber(123));        // true
console.log(isFiniteNumber(3.14));       // true
console.log(isFiniteNumber(NaN));        // false
console.log(isFiniteNumber(Infinity));   // false
console.log(isFiniteNumber("123"));      // false
JavaScript

この関数は短いですが、業務コードでは非常に使いやすいです。「型が number で、通常の計算に使える値だけを許可する」という意図が明確だからです。

業務で使える基本テンプレート その2:数値として読める文字列か判定する

次に、フォーム入力やCSVなど、文字列で受け取る数値を判定する関数です。

/**
 * 値が「数値として読める文字列」かを判定します。
 *
 * 仕様:
 * - 文字列だけを受け付けます。
 * - 前後の空白は無視します。
 * - 空文字や空白だけの文字列は false にします。
 * - Number() で有限数へ変換できる場合だけ true にします。
 *
 * @param {unknown} value 判定対象の値
 * @returns {boolean} 数値文字列なら true
 */
function isNumericString(value) {
  // 文字列以外は受け付けない
  if (typeof value !== "string") {
    return false;
  }

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

  // 空文字は数値として扱わない
  if (trimmed === "") {
    return false;
  }

  // 数値へ変換し、有限数かを確認する
  return Number.isFinite(Number(trimmed));
}

console.log(isNumericString("123"));      // true
console.log(isNumericString(" 3.14 "));   // true
console.log(isNumericString("-10"));      // true
console.log(isNumericString("1e3"));      // true
console.log(isNumericString(""));         // false
console.log(isNumericString("   "));      // false
console.log(isNumericString("abc"));      // false
console.log(isNumericString("123abc"));   // false
JavaScript

この関数は「文字列として入力された数値」を検証する用途に向いています。ただし、指数表記の "1e3" を許可したくない場合などは、別途ルールを追加します。

業務で使える完成版テンプレート:数値判定と用途別ルールを分ける

実務では、基本判定に加えて「整数だけ」「0以上だけ」などの条件が必要になります。そこで、まず文字列または数値を安全に数値へ変換し、その後で個別ルールを判定する形にすると使いやすくなります。

/**
 * 値を業務用の数値として正規化します。
 *
 * 仕様:
 * - 数値型は、そのまま使用します。
 * - 文字列は trim() 後に Number() で変換します。
 * - 空文字、空白だけの文字列、NaN、Infinity は不正とします。
 * - 変換できた場合は number を返し、不正な場合は null を返します。
 *
 * @param {unknown} value 入力値
 * @returns {number | null} 正常なら数値、失敗なら 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);

    // 有限数なら返し、そうでなければ null
    return Number.isFinite(converted) ? converted : null;
  }

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

/**
 * 値が有限の数値として利用できるか判定します。
 *
 * @param {unknown} value 入力値
 * @returns {boolean} 利用可能な数値なら true
 */
function isUsableNumber(value) {
  return toFiniteNumber(value) !== null;
}

/**
 * 値が0以上の整数として利用できるか判定します。
 *
 * @param {unknown} value 入力値
 * @returns {boolean} 0以上の整数なら true
 */
function isNonNegativeInteger(value) {
  const number = toFiniteNumber(value);

  // 正常に変換できなければ false
  if (number === null) {
    return false;
  }

  // 整数かつ 0 以上かを確認する
  return Number.isInteger(number) && number >= 0;
}

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

console.log(isUsableNumber("3.14"));    // true
console.log(isUsableNumber("abc"));     // false

console.log(isNonNegativeInteger("10"));   // true
console.log(isNonNegativeInteger("-1"));   // false
console.log(isNonNegativeInteger("3.14")); // false
JavaScript

この設計の利点は、変換のルールが1か所に集まることです。将来「指数表記は許可しない」「全角数字は事前変換する」といった仕様変更があっても、中心となる変換関数を見直すだけで済みやすくなります。

注意が必要なケースを深掘りする

NaN は自分自身と等しくない

NaN は特殊な値で、NaN === NaN が false になります。そのため、value === NaN で判定してはいけません。

// NaN は自分自身と一致しない
console.log(NaN === NaN); // false

// NaN 判定には Number.isNaN() を使う
console.log(Number.isNaN(NaN));     // true
console.log(Number.isNaN("NaN"));   // false
JavaScript

ただし、業務では NaN 単独を判定するより、Number.isFinite() で「通常の数値か」をまとめて判定するほうがわかりやすい場面が多いです。

空文字や null が 0 として扱われることがある

JavaScriptの自動変換は便利な反面、入力検証では落とし穴になります。

// Number() の変換結果
console.log(Number(""));     // 0
console.log(Number("   "));  // 0
console.log(Number(null));   // 0
console.log(Number(false));  // 0
JavaScript

フォームの未入力や欠損値を、意図せず 0 として扱ってしまうと、売上や件数の集計に誤りが入りやすくなります。「変換できるか」より前に、「その入力を受け付けてよいか」を考えることが重要です。

全角数字は自動で通らない

日本語入力では、"123" のような全角数字が入ることがあります。これは Number() で通常の数値へ変換できません。

// 全角数字はそのままでは数値にならない
console.log(Number("123")); // NaN
JavaScript

これを許可したい場合は、事前に半角へ正規化する処理を別で設計します。ただし、全角の記号や小数点、マイナス記号まで含めてどう扱うかが難しくなるため、入力UI側で制御するほうが簡単な場合も多いです。

BigInt は number とは別の型である

非常に大きな整数を扱う BigInt は、通常の数値型とは別です。

// BigInt は "bigint" 型
console.log(typeof 123n); // "bigint"

// Number.isFinite() では扱わない
console.log(Number.isFinite(123n)); // false
JavaScript

金額や件数の通常業務では number で十分なことが多いですが、IDや非常に大きな整数を扱う場合は、最初から別設計にする必要があります。

実務でよくある用途別の判定例

ページ番号: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("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
JavaScript

在庫数:0以上の整数

/**
 * 在庫数として妥当か判定します。
 *
 * @param {unknown} value 入力値
 * @returns {boolean} 0以上の整数なら true
 */
function isStockCount(value) {
  return isNonNegativeInteger(value);
}

console.log(isStockCount("0"));    // true
console.log(isStockCount("10"));   // true
console.log(isStockCount("-1"));   // false
console.log(isStockCount("2.5"));  // false
JavaScript

このように、基礎となる変換関数を1つ用意しておくと、用途別の関数を短く、読みやすく作れます。

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

数値判定の関数は短く見えても、境界条件が多いです。正常な数値だけでなく、空文字、空白、NaN、Infinity、文字列混在、指数表記なども確認します。

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

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

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

Object.is() を使うと、通常の === よりも一部の特殊値を厳密に比較できます。今回の期待値では主に null や通常の数値を扱っていますが、テストの書き方として覚えておくと便利です。

まとめ

JavaScriptで「数値かどうか」を判定するときは、まず何を数値として扱いたいのかを明確にすることが大切です。数値型の判定、文字列の変換可否、業務ルールの確認は、それぞれ別の段階として考えます。

  • 数値型として通常の数を判定するなら、Number.isFinite() が基本です。
  • 文字列を数値として受け取りたいなら、空文字を除外したうえで Number() と Number.isFinite() を組み合わせます。
  • parseInt() や parseFloat() は、厳密な入力検証の中心にしないほうが安全です。
  • NaN、Infinity、空文字、空白、全角数字などの扱いを明示します。
  • 基本の変換関数と、ページ番号、金額、在庫数などの用途別ルールを分けると保守しやすくなります。

実務で役立つのは、単に「数値かどうか」を判定することではなく、「どの入力を受け付け、どの値を安全に業務処理へ渡すか」を明確にすることです。小さなユーティリティでも、入力条件とテストをそろえることで、安心して再利用できるコードになります。

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