| 類型 | 位數/字節 | 有效數字(十進制) | 數值範圍 | 指數位/尾數位 |
float | 32 / 4 | 6~7 | ~3.4E-38 ~ 3.4E+38 | 8 / 23+1 |
double | 64 / 8 | 15~16 | ~1.7E-308 ~ 1.7E+308 | 11 / 52+1 |
long double | 80/128 / 10/16 | 18~19 / 33+ | ~1E-4932 ~ 1E+4932 | 15/112 / 63/112+1 |
聲明語法
float x, y; // 單精度
double a, b, c; // 雙精度 (推薦預設)
long double ld1, ld2; // 長雙精度
浮點數的捨入誤差:本質與應對
誤差來源
- 二進制無法精確表示十進制小數:0.1 十進制 = 0.0001100110011...₂ (無限循環)
- 尾數位有限:float 24 位、double 53 位有效二進制位,超出部分捨入
- 運算過程放大誤差:減法抵消、大數吃小數、累積誤差
經典範例 3-6:大數吃小數
- float a, b;
- a = 123456.789e5; // 約 1.23e10,超出 float 7 位有效數字
- b = a + 20; // 20 相對於 1e10 太小,被捨入!
- printf("%f\n%f\n", a, b); // 輸出相同!
經典範例 3-7:精度差異演示
- float a; double b;
- a = 33333.33333; // 整數 5 位,float 剩 2 位小數有效
- b = 33333.33333333333333; // double 15-16 位有效
- printf("%f\n%f\n", a, b);
輸出:
a:小數 2 位後全為垃圾值
b:小數 6 位後四捨五入 (Turbo C 默認 %f 輸出 6 位)
核心鐵律
- 1.0 / 3 * 3 ≠ 1.0 (浮點運算不滿足數學分配律/結合律)
- 浮點數直接比較 == 極其危險
- 累加迴圈誤差隨次數增長
浮點常量的類型規則
- 無後綴浮點常量 → 型別為
double
- 表達式求值:
float 參與運算通常提升為 double 進行 (C 標準要求)
- 常量不分單雙精度:全部按
double 處理,賦值給 float 時截斷
⚠️ 經典陷阱與避坑指南
🚫 陷阱 1:浮點數直接比較相等
float x = 0.1 + 0.2;
if (x == 0.3) // ❌ 幾乎必定假!
// 正確:誤差範圍比較
#include
#include
if (fabs(x - 0.3) < FLT_EPSILON) // 或自定義 EPS 如 1e-6
🚫 陷阱 2:整數除法後賦給浮點數
double avg = sum / n; // ❌ 先整數除法截斷
// 正確
double avg = (double)sum / n; // 或 sum / (double)n
🚫 陷阱 3:大數吃小數 (吸收誤差)
float huge = 1e10;
float tiny = 1.0;
float sum = huge + tiny; // sum == huge!tiny 被吸收
// 緩解:Kahan 求和算法、雙精度累加、分塊求和
🚫 陷阱 4:災難性抵消
double x = 1.0000001;
double y = 1.0000000;
double diff = x - y; // 相對誤差巨大!
// 重組算法避免相近大數相減
🚫 陷阱 5:printf/scanf 格式符不匹配
float f = 3.14f;
printf("%f", f); // ✅ float 提升為 double,%f 期望 double
scanf("%f", &f); // ❌ %f 期望 float*,需 %lf 給 double*
// 正確
scanf("%lf", &d); // double 用 %lf
scanf("%f", &f); // float 用 %f (scanf 必須區分!)
🚫 陷阱 6:循環累加誤差
float sum = 0;
for (int i = 0; i < 1000000; i++) sum += 0.1f; // 誤差累積
// 更好
double sum = 0;
for (int i = 0; i < 1000000; i++) sum += 0.1; // 雙精度減緩誤差
// 最佳:Kahan 求和
浮點數比較最佳實踐
相對誤差比較 (通用)
bool float_equal(double a, double b, double eps) {
if (a == b) return true; // 處理無窮、零
double diff = fabs(a - b);
double max_ab = fmax(fabs(a), fabs(b));
return diff / max_ab < eps;
}
ULP 比較 (專業級)
#include
bool float_equal_ulp(double a, double b, int max_ulp) {
// 比較兩浮點數表示的整數差值 (Units in Last Place)
// 需處理符號、NaN、無窮
}
最佳實踐清單
- 預設用
double:精度足夠、性能無損、避免 float 提升開銷
- 僅記憶體/帶寬敏感用
float:GPU、大數組、網絡傳輸
- 金融/精確計算用整數 (分/厘) 或十進制庫:避免二進制浮點誤差
- 比較用誤差範圍:
fabs(a-b) < eps
- scanf 區分 %f/%lf:
float 用 %f,double 用 %lf
- printf 統一 %f/%e/%g:
float/double 均可 (提升為 double)
- 常量加後綴:
3.14f, 3.14 (double), 3.14L
- 包含
<float.h> 使用 FLT_EPSILON, DBL_EPSILON
總結
C 語言浮點類型體系建立在 IEEE 754 二進制浮點數 基礎上,核心特徵:
- 三大類型:
float(32位/7位)、double(64位/15位)、long double(擴展)
- 常量預設 double,
float 需 f 後綴
- 運算提升為 double,賦值時截斷
- 本質誤差:二進制無法精確表示十進制、有限尾數、運算放大
- 鐵律:不直接比較 ==、警惕大數吃小數、災難性抵消
現代最佳實踐:預設 double、比較用 epsilon、金融用整數、累加用 Kahan、格式符嚴格匹配。深入理解浮點數本質,是從「能跑」進化到「數值穩健」的關鍵一步。
- 上一篇文章: C語言字符類型完整指南:字符常量、轉義字符、字符變量、內存存儲與字符串
- 下一篇文章: C語言整型數據完整教程:常量表示、變量分類、存儲與溢出
Copyright ©
程式碼之家 All Rights Reserved