float 与 double
float 和 double 是 C 语言的两种浮点类型,分别对应 IEEE 754 标准的单精度和双精度格式。它们用于表示实数,但由于二进制无法精确表示某些十进制小数,浮点数运算存在固有的精度限制。理解这些限制,是避免数值计算错误的关键。
float:单精度浮点
float 通常占用 4 字节(32 位),按 IEEE 754 标准编码:
- 1 位符号位
- 8 位指数位
- 23 位尾数位
float pi = 3.14159f; /* f 后缀表示 float 常量 */
float temperature = -15.5f;
printf("sizeof(float) = %zu\n", sizeof(float)); /* 通常是 4 */
float 的精度约为 7 位有效十进制数字,范围约 ±3.4×10³⁸:
#include <float.h>
printf("FLT_MIN = %e\n", FLT_MIN); /* 约 1.17×10⁻³⁸ */
printf("FLT_MAX = %e\n", FLT_MAX); /* 约 3.40×10³⁸ */
printf("FLT_EPSILON = %e\n", FLT_EPSILON); /* 约 1.19×10⁻⁷ */
double:双精度浮点
double 通常占用 8 字节(64 位),精度约为 15 位有效十进制数字,范围约 ±1.7×10³⁰⁸:
double pi = 3.141592653589793;
double avogadro = 6.02214076e23; /* 科学计数法 */
printf("sizeof(double) = %zu\n", sizeof(double)); /* 通常是 8 */
#include <float.h>
printf("DBL_MIN = %e\n", DBL_MIN); /* 约 2.22×10⁻³⁰⁸ */
printf("DBL_MAX = %e\n", DBL_MAX); /* 约 1.79×10³⁰⁸ */
printf("DBL_EPSILON = %e\n", DBL_EPSILON); /* 约 2.22×10⁻¹⁶ */
double 的精度是 float 的两倍有余,在科学计算、金融计算、图形渲染等对精度敏感的场景中,应优先使用 double。
浮点常量
浮点常量默认是 double 类型,加后缀可改变类型:
3.14 /* double */
3.14f /* float */
3.14F /* float */
3.14L /* long double */
3.14l /* long double(小写 l 易与 1 混淆,建议用大写 L) */
3.14e10 /* 科学计数法:3.14 × 10¹⁰ */
3.14E-5 /* 3.14 × 10⁻⁵ */
C99 新增十六进制浮点常量,用于精确表示 IEEE 754 浮点数的每一位:
float f = 0x1.0p0f; /* 1.0 × 2⁰ = 1.0 */
float g = 0x1.8p1f; /* 1.5 × 2¹ = 3.0 */
printf("%a\n", f); /* C99:十六进制浮点格式输出 */
十六进制浮点的语法:0x + 十六进制整数部分 + . + 十六进制小数部分 + p + 二进制指数。这种表示法避免了十进制到二进制的转换误差。
精度问题
浮点数无法精确表示某些十进制小数,这是二进制表示的固有局限:
printf("%.20f\n", 0.1); /* 0.10000000000000000555... */
printf("%.20f\n", 0.2); /* 0.20000000000000001110... */
if (0.1 + 0.2 == 0.3)
printf("Equal\n"); /* 通常不会输出! */
else
printf("Not equal\n"); /* 输出这个 */
正确比较浮点数:使用误差范围而非精确相等:
#include <math.h>
#define EPSILON 1e-9
int double_equal(double a, double b)
{
return fabs(a - b) < EPSILON;
}
if (double_equal(0.1 + 0.2, 0.3))
printf("Approximately equal\n"); /* 正确输出 */
对于接近零的比较,使用相对误差更合适:
int nearly_equal(double a, double b, double epsilon)
{
double diff = fabs(a - b);
double max_val = fmax(fabs(a), fabs(b));
if (max_val < epsilon) /* 两者都接近零 */
return diff < epsilon;
return diff / max_val < epsilon;
}
特殊浮点值
IEEE 754 定义了几个特殊值:
#include <math.h>
printf("%f\n", 1.0 / 0.0); /* inf:正无穷 */
printf("%f\n", -1.0 / 0.0); /* -inf:负无穷 */
printf("%f\n", 0.0 / 0.0); /* nan:非数字(Not a Number) */
/* C99 宏判断 */
double x = 0.0 / 0.0;
if (isnan(x)) printf("Not a number\n");
if (isinf(x)) printf("Infinity\n");
if (isfinite(x)) printf("Finite\n");
NaN 的特性:任何涉及 NaN 的比较都为假,包括 NaN == NaN。
double nan = 0.0 / 0.0;
if (nan == nan)
printf("NaN == NaN\n"); /* 不会输出! */
/* 判断 NaN 的正确方法 */
if (isnan(nan))
printf("This is NaN\n"); /* 正确输出 */
格式化输出
float f = 3.14159f;
double d = 3.141592653589793;
printf("%f\n", f); /* 默认 6 位小数:3.141590 */
printf("%.2f\n", f); /* 2 位小数:3.14 */
printf("%.10f\n", d); /* 10 位小数 */
printf("%e\n", d); /* 科学计数法:3.141593e+00 */
printf("%g\n", d); /* 自动选择 %f 或 %e */
printf("%a\n", d); /* C99:十六进制浮点 */
printf 的 %f 对应 double,float 参数会自动提升为 double。%Lf 对应 long double。
常见错误
精度损失:
float f = 0.1f; /* 存储的是近似值 */
float sum = 0.0f;
for (int i = 0; i < 1000000; i++)
sum += f; /* 累积误差 */
printf("%.6f\n", sum); /* 可能不是 100000.0 */
整数除法 vs 浮点除法:
int a = 5, b = 2;
float wrong = a / b; /* 整数除法:5/2 = 2,再转为 2.0 */
float right = (float)a / b; /* 浮点除法:2.5 */
比较浮点数是否相等:
if (x == 0.0) /* 危险:x 可能是 0.0000000001 */
/* ... */
if (fabs(x) < 1e-9) /* 安全:检查是否接近零 */
/* ... */