閉じる

【C言語】 FLT_MAXやDBL_MAXで見る範囲と桁数

浮動小数点型の最大値や精度は、実行環境によって微妙に異なります。

<float.h>は、その環境で使えるfloat、double、long doubleの範囲や桁数を教えてくれる標準ヘッダです。

本記事では、初心者の方でも迷わないように、代表的なマクロの意味と使い方、表示のコツ、よくある勘違いまで丁寧に解説します。

<float.h>の基本

何が分かるか

<float.h>には、実装が提供する浮動小数点型(IEEE 754が多いですが必須ではありません)の範囲・精度・表現形式に関するマクロが定義されています。

代表例を目的別に整理します。

  • 範囲(大きさ)に関するもの
    • FLT_MAXDBL_MAXLDBL_MAX…最大の有限値
    • FLT_MINDBL_MINLDBL_MIN…最小の正規化正の値
    • FLT_MIN_10_EXPFLT_MAX_10_EXP…10進指数の範囲(指数部の目安)
    • 実装が対応していればFLT_TRUE_MINなど…非正規数を含めた最小の正の値
  • 精度(桁数)に関するもの
    • FLT_DIGDBL_DIGLDBL_DIG…10進で信頼できる有効桁数の目安
    • FLT_EPSILONDBL_EPSILONLDBL_EPSILON…1と区別できる最小差(1付近の最小刻み)
    • DECIMAL_DIG…文字列往復(パース)で誤差を避けるために必要な桁数の目安
  • 表現形式に関するもの
    • FLT_RADIX…基数(ほとんどの環境で2)
    • FLT_MANT_DIGDBL_MANT_DIG…仮数部の桁数(基数<FLTRADIX>での桁数)
    • FLT_MIN_EXPFLT_MAX_EXP…基数<FLTRADIX>での指数範囲

これらの値はコンパイル時に確定し、ポータブルに参照できます

型の種類

C言語の浮動小数点型は3種類です。

  • float…軽量。多くの環境でIEEE 754のbinary32(約6〜7桁)。
  • double…基本。多くの環境でbinary64(約15〜16桁)。
  • long double…実装依存。x86系では80ビット拡張(約18〜19桁)が多い一方、環境によってはdoubleと同じこともあります。

同じソースでも、プラットフォームが変わると桁数や最大値が変わる可能性があるため、<float.h>のマクロで確認するのが安全です。

使い方

実用上は次のような場面で使います。

  • 入力の妥当性やアルゴリズムの閾値を型に応じて決める(例: 正規化の下限をDBL_MINで決定)。
  • 結果の表示桁数をDBL_DIGDECIMAL_DIGに合わせる。
  • 上限(overflow)・下限(underflow)の危険を事前に検知して回避する。

範囲を見る

最大の有限値

FLT_MAXDBL_MAXLDBL_MAXは、その型で表せる最大の有限値です。

これを超える演算は無限大(inf)に発散する可能性があります。

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

int main(void) {
    // %eは指数表記。floatは可変長引数でdoubleに昇格するため%f/%eでOK
    printf("FLT_MAX  = %.6e\n", FLT_MAX);
    printf("DBL_MAX  = %.6e\n", DBL_MAX);
    // long doubleは%Leや%Lgを使う
    printf("LDBL_MAX = %.6Le\n", LDBL_MAX);
    return 0;
}

例の出力(環境によって異なります):

FLT_MAX  = 3.402823e+38
DBL_MAX  = 1.797693e+308
LDBL_MAX = 1.189731e+4932

最小の正の数の目安

FLT_MINDBL_MIN最小の正規化(normal)の正の値です。

非正規数(denormal, subnormal)を含めた最小の正の値は<small>FLT_TRUE_MINDBL_TRUE_MIN</small>が定義されていれば取得できます。

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

int main(void) {
    printf("DBL_MIN (正規化の下限) = %.6e\n", DBL_MIN);
#ifdef DBL_TRUE_MIN
    printf("DBL_TRUE_MIN(非正規含む) = %.6e\n", DBL_TRUE_MIN);
#else
    puts("DBL_TRUE_MIN は未定義(処理系が未対応か非正規数非対応)");
#endif
    return 0;
}

多くのIEEE 754実装ではDBL_TRUE_MIN < DBL_MINとなり、DBL_MINの半分より小さい値でも非正規数として表現できます。

10進指数の範囲

FLT_MIN_10_EXPFLT_MAX_10_EXPは、10進の指数表現(科学技術表記)で使えるおおよその範囲を示します。

double版はDBL_MIN_10_EXP/DBL_MAX_10_EXPです。

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

int main(void) {
    printf("float: 10進指数の範囲 ≈ [%d, %d]\n", FLT_MIN_10_EXP, FLT_MAX_10_EXP);
    printf("double:10進指数の範囲 ≈ [%d, %d]\n", DBL_MIN_10_EXP, DBL_MAX_10_EXP);
    return 0;
}

この値が例えばdoubleで[-308, 308]なら、±d.ddd × 10^kの形でk = -308..+308程度が表現可能という目安になります。

範囲外計算に注意

オーバーフロー(上限超え)はinfになり、アンダーフロー(下限を大きく下回る)は0や非正規数になります。

境界の近くだと丸めの影響が強く出ます。

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

int main(void) {
    double a = DBL_MAX;
    double overflow = a * 2.0;     // 上限超え
    double b = DBL_MIN;
    double underflow = b / 2.0;    // 正規化の下限を下回る

    printf("DBL_MAX * 2 = %e\n", overflow);   // 多くの環境で inf と表示
    printf("DBL_MIN / 2 = %.20e\n", underflow); // 0 か 非正規数(非常に小さい値)
    return 0;
}
実行結果
DBL_MAX * 2 = inf
DBL_MIN / 2 = 1.11253692925360069120e-308

非正規数が有効な環境では0ではなく極小の値になります。

環境によってはunderflowが0になることもある点に注意してください。

桁数と精度

有効桁数の目安

FLT_DIGDBL_DIGLDBL_DIGは、10進で「ほぼ確実に信頼できる有効桁」の目安を与えます。

典型的にはfloatで6、doubleで15、long doubleで18前後です。

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

int main(void) {
    printf("float の有効桁目安  FLT_DIG  = %d\n", FLT_DIG);
    printf("doubleの有効桁目安  DBL_DIG  = %d\n", DBL_DIG);
    printf("long doubleの有効桁 LDBL_DIG = %d\n", LDBL_DIG);
    return 0;
}

表示の際に%.*gでこれらを使うと、過剰な桁を出して見た目の「誤差」を強調しすぎない印象にできます。

最小の差

DBL_EPSILONは「1.0と、1.0より大きい別のdoubleのうち最小のものとの差」です。

したがってすべての範囲での最小刻みを表すわけではありません

値が大きくなるほど刻みは粗くなります。

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

int main(void) {
    double one = 1.0;
    double a = one + DBL_EPSILON / 2.0; // 1との区別はつかないはず
    double b = one + DBL_EPSILON;       // 1と区別がつくはず

    printf("1 + DBL_EPSILON/2 == 1 ? %s\n", (a == one) ? "true" : "false");
    printf("1 + DBL_EPSILON   == 1 ? %s\n", (b == one) ? "true" : "false");

    double big = 1e308;
    double c = big + 1e292; // big付近では刻みが大きい(小さな加算は無視される可能性)
    printf("big == big + 1e292 ? %s\n", (c == big) ? "true" : "false");
    return 0;
}
実行結果
1 + DBL_EPSILON/2 == 1 ? true
1 + DBL_EPSILON   == 1 ? false
big == big + 1e292 ? true

このように、相対誤差の考え方が重要です。

比較や許容誤差の設計ではDBL_EPSILONを直接閾値にするのではなく、値の大きさに比例した許容誤差を検討します。

表示の目安

表示桁数は用途で使い分けます。

  • 人間に見せる概略値…DBL_DIG桁を目安に%.*g
  • 文字列↔数値の往復で情報を失いたくない…DECIMAL_DIG
C言語
#include <stdio.h>
#include <float.h>

int main(void) {
    double x = 1.0/10.0; // 0.1 は2進で有限表示できない例
    printf("概略表示(DBL_DIG桁): %.*g\n", DBL_DIG, x);
    printf("往復重視(DECIMAL_DIG桁): %.*g\n", DECIMAL_DIG, x);
    // 16進表記(%a)は正確な値(仮数と2進指数)をそのまま出せる
    printf("正確表示(16進浮動小数%%a): %a\n", x);
    return 0;
}
実行結果
概略表示(DBL_DIG桁): 0.1
往復重視(DECIMAL_DIG桁): 0.10000000000000001
正確表示(16進浮動小数%a): 0x1.999999999999ap-4

「0.1」が厳密に表せないのは、2進では有限桁にならないためです。

見せ方を適切に選ぶことが実務では重要です。

使い分けとサンプル

基本はdoubleを使う

初心者の方は、基本的にdoubleを使うのが安全です。

理由は次の通りです。

十分な有効桁(約15桁)、ライブラリ関数の多くがdoubleを中心に設計、そして速度・メモリのバランスが良いからです。

floatを選ぶ理由

floatはメモリ占有が小さく、大量配列やGPU、SIMDの帯域を重視する場面で有利です。

必要な精度が6〜7桁で足りる場合に検討します。

なお、可変長引数ではfloatdoubleに昇格するため、printfのフォーマットは%f/%eで問題ありません。

long doubleを選ぶ理由

long doubleより高い精度や広い範囲を狙うときに使います。

ただし実装依存が大きい点に注意が必要です。

x86では80ビット拡張でも、Windows/MSVCではdoubleと同等なことがあります。

printfでは%Lf/%Le/%Lgを使います。

マクロをprintfで表示する

<float.h>のマクロを実際に表示して、環境の性質を確認する小さなツールは有用です。

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

int main(void) {
    puts("=== 基本情報 ===");
    printf("FLT_RADIX = %d\n", FLT_RADIX);

    puts("\n=== float ===");
    printf("FLT_MAX = %.9e\n", FLT_MAX);
    printf("FLT_MIN = %.9e (正規化下限)\n", FLT_MIN);
#ifdef FLT_TRUE_MIN
    printf("FLT_TRUE_MIN = %.9e (非正規含む)\n", FLT_TRUE_MIN);
#else
    puts("FLT_TRUE_MIN = (未定義)");
#endif
    printf("FLT_DIG = %d, FLT_EPSILON = %.9e\n", FLT_DIG, FLT_EPSILON);
    printf("10進指数範囲 ≈ [%d, %d]\n", FLT_MIN_10_EXP, FLT_MAX_10_EXP);

    puts("\n=== double ===");
    printf("DBL_MAX = %.12e\n", DBL_MAX);
    printf("DBL_MIN = %.12e (正規化下限)\n", DBL_MIN);
#ifdef DBL_TRUE_MIN
    printf("DBL_TRUE_MIN = %.12e (非正規含む)\n", DBL_TRUE_MIN);
#else
    puts("DBL_TRUE_MIN = (未定義)");
#endif
    printf("DBL_DIG = %d, DBL_EPSILON = %.12e\n", DBL_DIG, DBL_EPSILON);
    printf("10進指数範囲 ≈ [%d, %d]\n", DBL_MIN_10_EXP, DBL_MAX_10_EXP);

    puts("\n=== long double ===");
    printf("LDBL_MAX = %.18Le\n", LDBL_MAX);
    printf("LDBL_MIN = %.18Le (正規化下限)\n", LDBL_MIN);
#ifdef LDBL_TRUE_MIN
    printf("LDBL_TRUE_MIN = %.18Le (非正規含む)\n", LDBL_TRUE_MIN);
#else
    puts("LDBL_TRUE_MIN = (未定義)");
#endif
    printf("LDBL_DIG = %d, LDBL_EPSILON = %.18Le\n", LDBL_DIG, LDBL_EPSILON);
    printf("10進指数範囲 ≈ [%d, %d]\n", LDBL_MIN_10_EXP, LDBL_MAX_10_EXP);

    puts("\n=== 表示の実践例 ===");
    double v = 1.0 / 10.0;
    printf("概略(DBL_DIG桁): %.*g\n", DBL_DIG, v);
    printf("往復(DECIMAL_DIG桁): %.*g\n", DECIMAL_DIG, v);
    printf("16進浮動小数: %a\n", v);
    return 0;
}

出力例(抜粋):

=== 基本情報 ===
FLT_RADIX = 2

=== float ===
FLT_MAX = 3.402823e+38
FLT_MIN = 1.175494e-38 (正規化下限)
FLT_TRUE_MIN = 1.401298e-45 (非正規含む)
FLT_DIG = 6, FLT_EPSILON = 1.192093e-07
10進指数範囲 ≈ [-37, 38]

=== double ===
DBL_MAX = 1.797693e+308
DBL_MIN = 2.225074e-308 (正規化下限)
DBL_TRUE_MIN = 4.940656e-324 (非正規含む)
DBL_DIG = 15, DBL_EPSILON = 2.220446e-16
10進指数範囲 ≈ [-307, 308]

=== long double ===
…(環境に依存)

よくある勘違い

  • DBL_MINは「最小(最も負)の値」ではないです。「最小の正の正規化値」です。最も負に近い有限値は-DBL_MAXです。
  • DBL_EPSILONは「どこでもの最小差」ではないです。1.0付近での最小差です。値のスケールが大きいと刻みも大きくなります。
  • 0.1や0.01は多くの環境で正確に表せません。表示桁数の選び方で見た目の誤差感が変わります。

実装依存に注意

<float.h>の値は実装依存です。

以下の点に注意してください。

  • long doubleの実装は環境により80ビット拡張、128ビット、あるいは64ビット(double同等)のことがあります。
  • 非正規数TRUE_MIN系マクロ(FLT_TRUE_MIN等)は、規格や実装のバージョンにより未定義のことがあります。
  • FLT_ROUNDSFLT_EVAL_METHODの値も処理系の最適化やFPU設定で変化する可能性があります。
  • ポータブルにコーディングするには、条件付きコンパイル(#ifdef)や、実行時にマクロの値を表示して確認するのが効果的です。

まとめ

<float.h>は、その環境での浮動小数点の「能力表」です。

最大値(FLT_MAX/DBL_MAX)や最小正規化値(FLT_MIN/DBL_MIN)、有効桁数(DBL_DIG)、最小差(DBL_EPSILON)などを理解すると、オーバーフロー・アンダーフローや表示上の誤差に慌てず対処できます。

実務では基本はdouble、必要に応じてfloatlong doubleを選び、表示桁数はDBL_DIGDECIMAL_DIGを使い分けるのが定石です。

実装依存は避けられないため、printfでマクロ値を確認する小さなユーティリティを用意しておくと安心です。

この記事を書いた人
エーテリア編集部
エーテリア編集部

プログラミングの基礎をしっかり学びたい方向けに、C言語の基本文法から解説しています。ポインタやメモリ管理も少しずつ理解できるよう工夫しています。

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

URLをコピーしました!