浮動小数点型の最大値や精度は、実行環境によって微妙に異なります。
<float.h>は、その環境で使えるfloat、double、long doubleの範囲や桁数を教えてくれる標準ヘッダです。
本記事では、初心者の方でも迷わないように、代表的なマクロの意味と使い方、表示のコツ、よくある勘違いまで丁寧に解説します。
<float.h>の基本
何が分かるか
<float.h>には、実装が提供する浮動小数点型(IEEE 754が多いですが必須ではありません)の範囲・精度・表現形式に関するマクロが定義されています。
代表例を目的別に整理します。
- 範囲(大きさ)に関するもの
FLT_MAX、DBL_MAX、LDBL_MAX…最大の有限値FLT_MIN、DBL_MIN、LDBL_MIN…最小の正規化正の値FLT_MIN_10_EXP、FLT_MAX_10_EXP…10進指数の範囲(指数部の目安)- 実装が対応していれば
FLT_TRUE_MINなど…非正規数を含めた最小の正の値
- 精度(桁数)に関するもの
FLT_DIG、DBL_DIG、LDBL_DIG…10進で信頼できる有効桁数の目安FLT_EPSILON、DBL_EPSILON、LDBL_EPSILON…1と区別できる最小差(1付近の最小刻み)DECIMAL_DIG…文字列往復(パース)で誤差を避けるために必要な桁数の目安
- 表現形式に関するもの
FLT_RADIX…基数(ほとんどの環境で2)FLT_MANT_DIG、DBL_MANT_DIG…仮数部の桁数(基数<FLTRADIX>での桁数)FLT_MIN_EXP、FLT_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_DIGやDECIMAL_DIGに合わせる。 - 上限(overflow)・下限(underflow)の危険を事前に検知して回避する。
範囲を見る
最大の有限値
FLT_MAX、DBL_MAX、LDBL_MAXは、その型で表せる最大の有限値です。
これを超える演算は無限大(inf)に発散する可能性があります。
#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_MINやDBL_MINは最小の正規化(normal)の正の値です。
非正規数(denormal, subnormal)を含めた最小の正の値は<small>FLT_TRUE_MINやDBL_TRUE_MIN</small>が定義されていれば取得できます。
#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_EXPとFLT_MAX_10_EXPは、10進の指数表現(科学技術表記)で使えるおおよその範囲を示します。
double版はDBL_MIN_10_EXP/DBL_MAX_10_EXPです。
#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や非正規数になります。
境界の近くだと丸めの影響が強く出ます。
#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_DIG、DBL_DIG、LDBL_DIGは、10進で「ほぼ確実に信頼できる有効桁」の目安を与えます。
典型的にはfloatで6、doubleで15、long doubleで18前後です。
#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のうち最小のものとの差」です。
したがってすべての範囲での最小刻みを表すわけではありません。
値が大きくなるほど刻みは粗くなります。
#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桁
#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桁で足りる場合に検討します。
なお、可変長引数ではfloatはdoubleに昇格するため、printfのフォーマットは%f/%eで問題ありません。
long doubleを選ぶ理由
long doubleはより高い精度や広い範囲を狙うときに使います。
ただし実装依存が大きい点に注意が必要です。
x86では80ビット拡張でも、Windows/MSVCではdoubleと同等なことがあります。
printfでは%Lf/%Le/%Lgを使います。
マクロをprintfで表示する
<float.h>のマクロを実際に表示して、環境の性質を確認する小さなツールは有用です。
#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_ROUNDSやFLT_EVAL_METHODの値も処理系の最適化やFPU設定で変化する可能性があります。
- ポータブルにコーディングするには、条件付きコンパイル(#ifdef)や、実行時にマクロの値を表示して確認するのが効果的です。
まとめ
<float.h>は、その環境での浮動小数点の「能力表」です。
最大値(FLT_MAX/DBL_MAX)や最小正規化値(FLT_MIN/DBL_MIN)、有効桁数(DBL_DIG)、最小差(DBL_EPSILON)などを理解すると、オーバーフロー・アンダーフローや表示上の誤差に慌てず対処できます。
実務では基本はdouble、必要に応じてfloatやlong doubleを選び、表示桁数はDBL_DIGやDECIMAL_DIGを使い分けるのが定石です。
実装依存は避けられないため、printfでマクロ値を確認する小さなユーティリティを用意しておくと安心です。
