整数非常适合用来计数,但有时候我们需要存储非常大的(正的或负的)数字,或者包含小数部分的数字。**浮点类型变量(floating point type)**是一种可以存储小数部分的变量,比如 4320.0
、-3.33
或 0.01226
。
名称中的 “floating” 指的是小数点可以"浮动" —— 也就是说,它可以支持小数点前后可变数量的数字。浮点类型数据始终是带符号的(即可以为正或负)。
💡 提示
在代码中编写浮点数时,小数分隔符必须是英文句点(.
)。如果你来自一个使用小数逗号的国家(如5,2
表示 5.2),你需要习惯使用英文句点作为小数点。
C++浮点类型详解
C++ 提供了三种基本的浮点数类型:单精度 float、双精度 double 和 扩展精度 long double。 和整数一样,C++ 并未定义这些类型的实际大小。
分类 | C++ 类型 | 典型大小 |
---|---|---|
浮点数 | float | 4 字节 |
双精度浮点数 | double | 8 字节 |
长双精度浮点 | long double | 8、12 或 16 字节 |
在现代架构中,浮点类型通常遵循 IEEE 754 标准格式(详见 Wikipedia: IEEE 754)。因此,float
几乎总是 4 字节,double
几乎总是 8 字节。
而 long double
就比较奇怪了。在不同平台上,其大小可能从 8 到 16 字节不等,而且可能并不遵循 IEEE 754 标准。推荐避免使用 long double
。
💡 提示
本教程假设你的编译器为float
和double
使用 IEEE 754 兼容格式。
你可以用以下代码检查你的浮点类型是否符合 IEEE 754:
#include <iostream>
#include <limits>
int main()
{
std::cout << std::boolalpha;
std::cout << "float: " << std::numeric_limits<float>::is_iec559 << '\n';
std::cout << "double: " << std::numeric_limits<double>::is_iec559 << '\n';
std::cout << "long double: " << std::numeric_limits<long double>::is_iec559 << '\n';
}
浮点数标准实现详解
float
几乎总是使用 IEEE 754 单精度(4 字节)格式。double
几乎总是使用 IEEE 754 双精度(8 字节)格式。
但 long double
的实现因平台而异,常见情况包括:
- 8 字节的 IEEE 754 双精度格式(与
double
相同) - 80 位的 x87 扩展精度格式(通常填充至 12 或 16 字节)
- 16 字节的 IEEE 754 四倍精度格式
- 16 字节的 double-double 格式(不符合 IEEE 754)
浮点数的声明与初始化
示例代码:
float f;
double d;
long double ld;
在使用浮点字面量时,一定要写小数点(即使是 .0
),以避免被识别为整数。
int a { 5 }; // 5 是整数
double b { 5.0 }; // 默认类型是 double
float c { 5.0f }; // f 后缀表示 float 类型
注意:默认情况下,浮点字面量为 double
类型。如果你需要 float
,请添加 f
后缀。
✅ 最佳实践
请确保字面量的类型与变量的类型一致。否则将发生隐式类型转换,可能导致精度丢失。
浮点数的输出格式
示例程序:
#include <iostream>
int main()
{
std::cout << 5.0 << '\n';
std::cout << 6.7f << '\n';
std::cout << 9876543.21 << '\n';
return 0;
}
输出结果可能如下:
5
6.7
9.87654e+06
- 第一个结果省略了
.0
,因为默认不打印 0 的小数部分。 - 第三个数字自动使用了科学计数法(你可以回顾前文关于科学计数法的章节)。
浮点数的精度与范围
格式 | 范围 | 精度(有效位数) |
---|---|---|
IEEE 754 单精度 (float) | ±1.18 × 10⁻³⁸ ~ ±3.4 × 10³⁸ 和 0.0 | 6~9 位,典型为 7 位 |
IEEE 754 双精度 (double) | ±2.23 × 10⁻³⁰⁸ ~ ±1.80 × 10³⁰⁸ 和 0.0 | 15~18 位,典型为 16 位 |
x87 扩展精度 (80 位) | ±3.36 × 10⁻⁴⁹³² ~ ±1.18 × 10⁴⁹³² 和 0.0 | 18~21 位 |
IEEE 754 四倍精度 (16 字节) | ±3.36 × 10⁻⁴⁹³² ~ ±1.18 × 10⁴⁹³² 和 0.0 | 33~36 位 |
浮点数的精度问题与舍入误差
比如我们考虑分数 1/3
,它的十进制为 0.3333...
,3 无限重复。人手写时会停下来,电脑也是一样。由于内存有限(通常 4 或 8 字节),浮点数只能保留有限位有效数字,其余位会舍入或丢失。
#include <iostream>
#include <iomanip>
int main()
{
std::cout << std::setprecision(17);
std::cout << 3.33333333333333333333333333333f << '\n'; // float
std::cout << 3.33333333333333333333333333333 << '\n'; // double
return 0;
}
输出:
3.3333332538604736
3.3333333333333335
- float 类型存在更大误差。
- double 类型更精确(但依然不是完美)。
浮点数计算中的常见问题
float f { 123456789.0f }; // 有 10 位有效数字
std::cout << std::setprecision(9);
std::cout << f << '\n'; // 输出可能为 123456792
- 这里
f
超过了 float 类型的精度(通常为 7 位),导致 舍入误差(rounding error)。
✅ 最佳实践
除非非常关注内存占用,优先使用double
,以避免精度问题。
浮点数比较的陷阱
我们习惯认为 0.1 + 0.1 + 0.1 × 10 = 1.0
,但实际上不一定成立:
double d1{ 1.0 };
double d2{ 0.1 + 0.1 + ... + 0.1 (共10个) };
std::cout << d1 << '\n'; // 1
std::cout << d2 << '\n'; // 0.99999999999999989
- 原因在于
0.1
无法被精确地用二进制表示,只能近似表示为0.000110011001100...
- 多次运算后,误差累积导致
d2 != d1
。
关键注意事项
- 舍入误差是浮点数的常态,而不是例外。
- 永远不要假设浮点数是精确的!
- 不要在金融或货币数据中使用浮点数。
特殊浮点值:NaN和Inf
符合 IEEE 754 的浮点格式支持以下特殊值:
- Inf:表示无穷大,可为正或负。
- NaN:表示"不是一个数字",用于表示非法数学运算(如
0.0 / 0.0
)。 - 有符号零:即
+0.0
和-0.0
是不同的。
示例:
double zero { 0.0 };
double posinf { 5.0 / zero };
double neginf { -5.0 / zero };
double nan { zero / zero };
std::cout << posinf << '\n'; // inf
std::cout << neginf << '\n'; // -inf
std::cout << nan << '\n'; // nan
注意:这些输出在不同编译器下可能表现不同(如 VS 会显示为 -nan(ind)
)。
✅ 最佳实践
避免除以0.0
,即使编译器支持它。
浮点数使用总结
记住以下两点:
- 浮点数适用于存储非常大或非常小、带小数部分的数值;
- 浮点数存在精度误差,甚至在有效数字少于精度限制时也可能出现舍入误差。
这些误差往往微小,不易察觉,尤其在输出时被自动截断。但它们确实存在,并且可能在比较和运算中造成问题。