閉じる

C言語の無限大・NaN完全ガイド 判定・比較・例外処理まで解説

C言語で浮動小数点を扱っていると、計算結果が非常に大きくなりすぎて無限大になったり、0で割るなどしてNaN(Not a Number)が発生したりします。

これらを正しく判定・比較しないと、バグや予期せぬ動作につながります。

本記事では、C言語の無限大とNaNの基本から、標準的な判定方法、例外処理の考え方、実用的なサンプルコードまで、丁寧に解説していきます。

C言語における無限大とNaNとは何か

無限大とNaNの概要

無限大(infinity)とは

無限大(infinity)とは、浮動小数点計算で表現できる最大値を超えてしまった結果として表される「限りなく大きい値」の記号的な表現です。

C言語では、主にIEEE 754という浮動小数点標準に基づいて無限大を表現しています。

代表的な発生例として、次のようなものがあります。

  • 非ゼロ値を0.0で割った場合
  • 非常に大きな値同士の乗算でオーバーフローした場合
  • 指数関数などで演算結果が範囲を超えた場合

浮動小数点には正の無限大負の無限大があります。

NaN(Not a Number)とは

一方、NaN(Not a Number)は「数として意味を持たない結果」を表す特別な値です。

算術的に定義されない演算、あるいは無効な演算の結果として生成されます。

代表的な発生例は次の通りです。

  • 0 / 0.0
  • 無限大同士の引き算(∞ − ∞)
  • 負の数の平方根(sqrt(-1.0))など
  • NaNを含む計算(多くの場合、結果もNaNになります)

NaNは「エラー値」ではなく「伝播する特殊な数値」という点が重要です。

無限大・NaNが必要になる理由

計算を途中で止めず「状態」として伝えるため

もし無限大やNaNという概念がなければ、オーバーフローや不正計算が起こるたびに即座にプログラムを止めるか、あるいは適当な最大値で打ち切らなければなりません。

無限大やNaNを導入することで、「異常な状態」を数値として表現し、後続処理へと伝えることができます

これにより、後から検査してログを取ったり、特別な処理に分岐させたりすることが可能になります。

数値計算での安全性とデバッグのしやすさ

特に数値シミュレーションや統計分析では、計算の途中で極端な値が出ることは珍しくありません。

無限大やNaNを適切に判定することで、次のような利点があります。

  • 異常値にいち早く気づける
  • 異常が発生した「位置」を特定しやすくなる
  • エラー発生後も、ログ出力や後処理を安全に行える

「想定外の0除算」や「負数の平方根」などを見逃さないためにも、無限大とNaNの扱いを理解することは非常に重要です。

IEEE 754とC言語の関係

IEEE 754とは

IEEE 754は、浮動小数点数の表現と演算ルールを定めた国際標準規格です。

C言語のfloatdouble型は、ほとんどの環境でこのIEEE 754に従って実装されています。

IEEE 754では、通常の数値だけでなく、無限大やNaN、さらに符号付きゼロ(+0.0と-0.0)なども定義されています

C言語でIEEE 754が前提になる理由

C言語の標準仕様自体は「必ずIEEE 754であれ」とは言っていませんが、現代の一般的なCPUとコンパイラ環境では、IEEE 754が事実上の前提となっています。

したがって、無限大やNaNの挙動について解説する際も、IEEE 754準拠の実装を仮定して話を進めます

無限大の生成と判定

無限大を生成する方法

標準ヘッダによる生成(math.h)

最も標準的な方法は、math.hに定義されているINFINITYマクロを使う方法です。

C言語
#include <stdio.h>
#include <math.h>

int main(void) {
    // math.h で定義されている無限大
    float  pos_inf_f = INFINITY;   // float型の正の無限大
    double pos_inf   = INFINITY;   // doubleへも代入可能

    // 負の無限大は符号をつけて生成
    double neg_inf   = -INFINITY;

    printf("pos_inf_f = %f\n", pos_inf_f);
    printf("pos_inf   = %f\n", pos_inf);
    printf("neg_inf   = %f\n", neg_inf);

    return 0;
}
実行結果
pos_inf_f = inf
pos_inf   = inf
neg_inf   = -inf

多くの実装ではprintfで無限大を出力するとinfinfinityと表示されます

計算結果としての無限大

計算の結果として無限大が発生する典型例です。

C言語
#include <stdio.h>
#include <math.h>

int main(void) {
    double a = 1.0;
    double b = 0.0;

    // 非ゼロ / 0.0 は正または負の無限大 (実装依存の例外発生の可能性あり)
    double c = a / b;   // 1.0 / 0.0 → +inf (IEEE 754想定)

    double big = 1e308;
    double d = big * big; // オーバーフロー → +inf が典型的な挙動

    printf("a / b  = %f\n", c);
    printf("big*big= %f\n", d);

    return 0;
}
実行結果
a / b  = inf
big*big= inf

無限大かどうかを判定する

isinf関数による判定

無限大判定にはmath.hで定義されているisinf関数を使います。

C言語
#include <stdio.h>
#include <math.h>

void check_value(double x) {
    if (isinf(x)) {
        if (x > 0) {
            printf("値は正の無限大です\n");
        } else {
            printf("値は負の無限大です\n");
        }
    } else {
        printf("値は無限大ではありません (x = %f)\n", x);
    }
}

int main(void) {
    double x1 = INFINITY;   // 正の無限大
    double x2 = -INFINITY;  // 負の無限大
    double x3 = 123.45;     // 通常の値

    check_value(x1);
    check_value(x2);
    check_value(x3);

    return 0;
}
実行結果
値は正の無限大です
値は負の無限大です
値は無限大ではありません (x = 123.450000)

isinfは、引数が正または負の無限大であれば非0(真)を返し、それ以外なら0(偽)を返します

isfiniteで「有限かどうか」を判定

逆に、isfinite関数を使うと「無限大でもNaNでもない通常の有限値かどうか」をまとめて判定できます。

C言語
#include <stdio.h>
#include <math.h>

void classify(double x) {
    if (isnan(x)) {
        printf("NaN です\n");
    } else if (isinf(x)) {
        printf("無限大です\n");
    } else if (isfinite(x)) {
        printf("有限値です (x = %f)\n", x);
    } else {
        printf("その他の値です\n");
    }
}

int main(void) {
    double a = 1.0 / 0.0;     // +inf (想定)
    double b = 0.0 / 0.0;     // NaN  (想定)
    double c = 123.456;       // 有限値

    classify(a);
    classify(b);
    classify(c);

    return 0;
}
実行結果
無限大です
NaN です
有限値です (x = 123.456000)

図解で理解する無限大の位置づけ

この図では、通常の実数値が実数直線上の有限な区間に収まっているのに対し、無限大はその両端の「果て」を象徴する記号として描かれます。

一方、NaNはそもそも数ではないため、実数直線から少し離れた場所に「別枠」として表現します。

このイメージを持っておくと、無限大は「とても大きな数の延長」にありますが、NaNは「数の世界の外」にあることが直感的に理解しやすくなります。

NaNの生成と判定

NaNを生成する方法

NANマクロを使う

math.hにはNANというマクロが定義されており、浮動小数点のNaN値を直接生成できます。

C言語
#include <stdio.h>
#include <math.h>

int main(void) {
    float  nan_f = NAN;   // float型のNaN
    double nan_d = NAN;   // doubleにも代入できる

    printf("nan_f = %f\n", nan_f);
    printf("nan_d = %f\n", nan_d);

    return 0;
}
実行結果
nan_f = nan
nan_d = nan

不正な演算からNaNを発生させる

次のような演算は、多くのIEEE 754実装でNaNを生成します。

C言語
#include <stdio.h>
#include <math.h>

int main(void) {
    double x1 = 0.0 / 0.0;        // 0/0 → NaN (想定)
    double x2 = INFINITY - INFINITY; // ∞ - ∞ → NaN (想定)
    double x3 = sqrt(-1.0);       // 負数の平方根 → NaN (想定)

    printf("0.0 / 0.0        = %f\n", x1);
    printf("INFINITY - INFINITY = %f\n", x2);
    printf("sqrt(-1.0)       = %f\n", x3);

    return 0;
}
実行結果
0.0 / 0.0        = nan
INFINITY - INFINITY = nan
sqrt(-1.0)       = nan

NaNがいったん発生すると、多くの演算でその結果もNaNになります

これを「NaNの伝播」と呼びます。

NaNを判定するisnan

NaNかどうかを判定するにはmath.hisnan関数を使います。

C言語
#include <stdio.h>
#include <math.h>

void check_nan(double x) {
    if (isnan(x)) {
        printf("値は NaN です\n");
    } else {
        printf("値は NaN ではありません (x = %f)\n", x);
    }
}

int main(void) {
    double a = 0.0 / 0.0;  // NaN (想定)
    double b = 1.0 / 0.0;  // +inf (想定)
    double c = 42.0;       // 有限値

    check_nan(a);
    check_nan(b);
    check_nan(c);

    return 0;
}
実行結果
値は NaN です
値は NaN ではありません (x = inf)
値は NaN ではありません (x = 42.000000)

isnanは、引数がNaNなら非0(真)を返し、それ以外の値(無限大や有限値を含む)なら0(偽)を返します

NaNと比較演算の落とし穴

NaNはどの値とも等しくない

NaNの最も重要な性質は「NaNは、自分自身とも等しくない」という点です。

したがって、次のようなコードはNaN判定としては誤りです。

C言語
double x = 0.0 / 0.0; // NaN (想定)

// これは NaN 判定としては誤り
if (x == NAN) {
    // ここには決して到達しません
}

NaN同士を==で比較してもtrueにはなりません

NaNを判定したい場合はisnanを必ず使います。

自己比較でNaNを検出するトリック

標準的にはisnanを使うべきですが、「値が自分自身と等しくないなら、それはNaNである」という性質を利用することもできます。

C言語
#include <stdio.h>

int main(void) {
    double x = 0.0 / 0.0;   // NaN (想定)

    if (x != x) {
        printf("xはNaNと判定されました (x != x)\n");
    } else {
        printf("xはNaNではありません\n");
    }

    return 0;
}
実行結果
xはNaNと判定されました (x != x)

ただし、この方法は挙動が実装依存になる可能性もあるため、原則としてisnanの使用を推奨します。

図解で理解するNaNの比較挙動

この図では、通常の数同士であれば大小比較や等価比較が意味を持つ一方、NaNが1つでも含まれた比較は「すべてfalseになる(ただし!=のみtrue)」ことを直感的に示します。

視覚的に整理しておくことで、「なぜNaNとの比較結果が期待通りにならないのか」を理解しやすくなります。

無限大・NaNと比較演算の実際

無限大の比較演算挙動

無限大同士の比較

IEEE 754を前提とすると、無限大同士は通常のルールに従って比較されます。

  • INFINITY == INFINITYtrue
  • -INFINITY == -INFINITYtrue
  • INFINITY > 任意の有限値true
  • -INFINITY < 任意の有限値true
C言語
#include <stdio.h>
#include <math.h>

int main(void) {
    double p = INFINITY;
    double n = -INFINITY;
    double x = 1.0;

    printf("INFINITY == INFINITY : %d\n", p == INFINITY);
    printf("-INFINITY == -INFINITY : %d\n", n == -INFINITY);
    printf("INFINITY > 1.0 : %d\n", p > x);
    printf("-INFINITY < 1.0 : %d\n", n < x);

    return 0;
}
実行結果
INFINITY == INFINITY : 1
-INFINITY == -INFINITY : 1
INFINITY > 1.0 : 1
-INFINITY < 1.0 : 1

NaNが比較に与える影響

NaNが含まれると大小比較はすべてfalse

NaNを含む比較の挙動を確認してみます。

C言語
#include <stdio.h>
#include <math.h>

int main(void) {
    double x = NAN;
    double y = 1.0;

    printf("x == x : %d\n", x == x);
    printf("x != x : %d\n", x != x);
    printf("x < y  : %d\n", x < y);
    printf("x > y  : %d\n", x > y);
    printf("x <= y : %d\n", x <= y);
    printf("x >= y : %d\n", x >= y);

    return 0;
}
実行結果
x == x : 0
x != x : 1
x < y  : 0
x > y  : 0
x <= y : 0
x >= y : 0

NaN同士の比較も同様に、すべての大小比較はfalseになります

ソートや最大値・最小値での注意点

NaNを含む配列のソート

浮動小数点配列をソートする際にNaNが含まれていると、比較関数が「常にfalseを返す」パターンが発生し、並べ替えアルゴリズムが想定通り動かないことがあります。

ソートの前にNaNを除外または別扱いするか、比較関数の中でisnanを用いて明示的な順序付けをすることが重要です。

比較・判定のまとめ表

無限大・NaNと比較演算の関係を表に整理します。

値の組み合わせ結果(典型例)説明
有限値 vs 有限値1.0 < 2.0true通常の比較
無限大 vs 有限値INFINITY > 1.0true無限大は任意の有限値より大きい
無限大 vs 無限大INFINITY == INFINITYtrue同じ符号の無限大同士は等しい
NaN vs 任意の値x == y (NaN含む)falseNaNとの等価比較は常にfalse
NaN vs 任意の値x != y (NaN含む)true!= だけがtrueになる
NaN vs 任意の値x < y, x > yfalse大小比較は意味を持たない

NaNは「比較では判定せずisnanで判定」「無限大はisinfで判定し、必要なら比較も併用」と覚えておくと実装が安定します

例外処理とエラー検出の考え方

IEEE 754の例外とC言語の関係

IEEE 754では、次のような例外的状況が定義されています。

  • オーバーフロー
  • アンダーフロー
  • 無効な演算(0/0、∞−∞など)
  • ゼロ除算
  • 不正確(丸めにより精度が落ちる)

しかしC言語標準では、これらの例外を「例外ハンドラ」などで直接扱う仕組みは義務付けていません

実装によってはfenv.hを使ってフラグを取得できますが、環境依存の色が濃くなります。

実用上のエラー検出パターン

1. 戻り値とerrnoを確認する(数学関数)

一部の数学関数は、エラー時にerrnoをセットします

例としてlog関数で不正な引数を渡した場合などです。

C言語
#include <stdio.h>
#include <math.h>
#include <errno.h>

int main(void) {
    errno = 0;                 // 事前にクリア
    double x = -1.0;
    double y = log(x);         // log(-1) は定義されない → NaN (想定)

    if (errno != 0) {
        perror("log error");
    }

    if (isnan(y)) {
        printf("結果は NaN です\n");
    }

    return 0;
}
実行結果
log error: Numerical argument out of domain
結果は NaN です

errnoに頼るかどうかは実装やコンパイルオプションにも依存するため、ドキュメントを確認の上で併用することをおすすめします。

2. 計算結果にisnan・isinfをかける

数値計算では「重要なステップの直後でisnanisinfによるチェックを入れる」ことがよく行われます。

C言語
#include <stdio.h>
#include <math.h>

double safe_div(double a, double b) {
    double result = a / b;

    if (isnan(result)) {
        printf("警告: a/b の結果が NaN になりました (a=%f, b=%f)\n", a, b);
    } else if (isinf(result)) {
        printf("警告: a/b の結果が無限大になりました (a=%f, b=%f)\n", a, b);
    }

    return result;
}

int main(void) {
    safe_div(1.0, 0.0);   // 無限大 (想定)
    safe_div(0.0, 0.0);   // NaN (想定)
    safe_div(1.0, 2.0);   // 正常

    return 0;
}
実行結果
警告: a/b の結果が無限大になりました (a=1.000000, b=0.000000)
警告: a/b の結果が NaN になりました (a=0.000000, b=0.000000)

NaN・無限大発生時の対処戦略

戦略1: その場でエラーとして処理を中断する

クリティカルな計算では、NaNや無限大を検出した時点で即座に処理を中断し、エラーとして上位へ伝えるのが安全です。

  • ログを出力し、異常値を含む入力を記録する
  • エラーコードを呼び出し元に返す
  • 必要ならプログラムを終了させる

戦略2: 代替値を用いて計算を継続する

リアルタイム処理やUI表示用の計算など、「停止よりも継続が優先される」場面では、以下のような代替値を用いることがあります。

  • NaNを0.0に置き換える
  • 無限大を上限値・下限値にクリップする
  • 前回の有効な値を再利用する
C言語
#include <stdio.h>
#include <math.h>

double sanitize(double x) {
    if (isnan(x)) {
        return 0.0;        // NaN → 0.0 に置き換え
    }
    if (x > 1e6) {         // 上限を設定
        return 1e6;
    }
    if (x < -1e6) {        // 下限を設定
        return -1e6;
    }
    return x;              // 問題なし
}

int main(void) {
    double values[] = { NAN, INFINITY, -INFINITY, 100.0 };
    size_t n = sizeof(values) / sizeof(values[0]);

    for (size_t i = 0; i < n; i++) {
        double s = sanitize(values[i]);
        printf("raw=%f, sanitized=%f\n", values[i], s);
    }

    return 0;
}
実行結果
raw=nan, sanitized=0.000000
raw=inf, sanitized=1000000.000000
raw=-inf, sanitized=-1000000.000000
raw=100.000000, sanitized=100.000000

どの戦略を採用するかは、アプリケーションの要件と安全性のバランスを考慮して決める必要があります

実践的なサンプルコード集

入力値の検証付き計算関数

例: 安全な平方根関数

C言語
#include <stdio.h>
#include <math.h>

// 入力値を検証しながら平方根を計算する関数
// 戻り値: 正常な場合 0、異常な場合 非0
int safe_sqrt(double x, double *out_result) {
    if (x < 0.0) {
        // 負の数の平方根は定義されない
        fprintf(stderr, "エラー: 負の入力に対するsqrt(x)です (x=%f)\n", x);
        *out_result = NAN;
        return -1;
    }

    double r = sqrt(x);

    if (isnan(r) || isinf(r)) {
        fprintf(stderr, "エラー: sqrtの結果がNaNまたは無限大です (x=%f)\n", x);
        *out_result = NAN;
        return -2;
    }

    *out_result = r;
    return 0;
}

int main(void) {
    double inputs[] = { 4.0, -1.0, INFINITY };
    size_t n = sizeof(inputs) / sizeof(inputs[0]);

    for (size_t i = 0; i < n; i++) {
        double x = inputs[i];
        double r;
        int err = safe_sqrt(x, &r);

        if (err == 0) {
            printf("sqrt(%f) = %f\n", x, r);
        } else {
            printf("sqrt(%f) に失敗しました (err=%d)\n", x, err);
        }
    }

    return 0;
}
実行結果
sqrt(4.000000) = 2.000000
エラー: 負の入力に対するsqrt(x)です (x=-1.000000)
sqrt(-1.000000) に失敗しました (err=-1)
エラー: sqrtの結果がNaNまたは無限大です (x=inf)
sqrt(inf) に失敗しました (err=-2)

この例では、入力値に対する事前検証と結果に対する事後検証を両方行うことで、安全なインターフェースを構成しています。

配列内のNaN・無限大を検出するユーティリティ

C言語
#include <stdio.h>
#include <math.h>

// 配列内にNaNまたは無限大が含まれているか調べる
int has_nan_or_inf(const double *arr, size_t n) {
    for (size_t i = 0; i < n; i++) {
        if (isnan(arr[i]) || isinf(arr[i])) {
            return 1;   // 見つかった
        }
    }
    return 0;           // 見つからなかった
}

int main(void) {
    double a[] = { 1.0, 2.0, 3.0 };
    double b[] = { 1.0, NAN, 3.0 };
    double c[] = { 1.0, INFINITY, 3.0 };

    printf("a: %d\n", has_nan_or_inf(a, 3));
    printf("b: %d\n", has_nan_or_inf(b, 3));
    printf("c: %d\n", has_nan_or_inf(c, 3));

    return 0;
}
実行結果
a: 0
b: 1
c: 1

このような簡易チェック関数を用意しておけば、大量のデータを扱う前に「安全性チェック」を一括で実行できます。

JSONやテキスト出力時にNaN・無限大を文字列に変換

多くのフォーマット(JSONなど)では、NaNや無限大をそのまま数値として書き出すと非標準な表現になってしまいます。

そのため、文字列に変換して扱う方法がよく利用されます。

C言語
#include <stdio.h>
#include <math.h>

void print_value_for_json(double x) {
    if (isnan(x)) {
        printf("\"NaN\"");
    } else if (isinf(x)) {
        if (x > 0) {
            printf("\"Infinity\"");
        } else {
            printf("\"-Infinity\"");
        }
    } else {
        printf("%f", x);
    }
}

int main(void) {
    double values[] = { 1.23, NAN, INFINITY, -INFINITY };
    size_t n = sizeof(values) / sizeof(values[0]);

    printf("[");
    for (size_t i = 0; i < n; i++) {
        if (i > 0) printf(", ");
        print_value_for_json(values[i]);
    }
    printf("]\n");

    return 0;
}
実行結果
["1.230000", "NaN", "Infinity", "-Infinity"]

この例では単純なJSON風表現ですが、アプリケーション間で数値をやり取りする際にNaN・無限大をどう表現するかを事前に取り決めることが重要です。

まとめ

C言語の無限大とNaNは、一見すると特殊で難解に感じられますが、IEEE 754という決めごとに従っている「状態付きの浮動小数点値」だと理解すると整理しやすくなります。

無限大は「範囲外に発散した値」、NaNは「数として意味を持たない結果」を表現し、それぞれisinfisnanで判定します。

特にNaNは比較演算では正しく検出できないため、必ず専用の判定関数を使うことが重要です。

数値計算やデータ処理では、重要な演算直後にNaN・無限大をチェックし、ログ出力や代替値への置き換えなどの方針を一貫して適用することで、バグの早期発見と信頼性向上につながります。

本記事を参考に、無限大とNaNを正しく扱える堅牢なCプログラムを設計してみてください。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!