C++浮点数完全指南:精度、范围与最佳实践

整数非常适合用来计数,但有时候我们需要存储非常大的(正的或负的)数字,或者包含小数部分的数字。**浮点类型变量(floating point type)**是一种可以存储小数部分的变量,比如 4320.0-3.330.01226。 名称中的 “floating” 指的是小数点可以"浮动" —— 也就是说,它可以支持小数点前后可变数量的数字。浮点类型数据始终是带符号的(即可以为正或负)。

💡 提示
在代码中编写浮点数时,小数分隔符必须是英文句点(.)。如果你来自一个使用小数逗号的国家(如 5,2 表示 5.2),你需要习惯使用英文句点作为小数点。

C++浮点类型详解

C++ 提供了三种基本的浮点数类型:单精度 float双精度 double扩展精度 long double。 和整数一样,C++ 并未定义这些类型的实际大小。

分类C++ 类型典型大小
浮点数float4 字节
双精度浮点数double8 字节
长双精度浮点long double8、12 或 16 字节

在现代架构中,浮点类型通常遵循 IEEE 754 标准格式(详见 Wikipedia: IEEE 754)。因此,float 几乎总是 4 字节,double 几乎总是 8 字节。

long double 就比较奇怪了。在不同平台上,其大小可能从 8 到 16 字节不等,而且可能并不遵循 IEEE 754 标准。推荐避免使用 long double

💡 提示
本教程假设你的编译器为 floatdouble 使用 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.06~9 位,典型为 7 位
IEEE 754 双精度 (double)±2.23 × 10⁻³⁰⁸ ~ ±1.80 × 10³⁰⁸ 和 0.015~18 位,典型为 16 位
x87 扩展精度 (80 位)±3.36 × 10⁻⁴⁹³² ~ ±1.18 × 10⁴⁹³² 和 0.018~21 位
IEEE 754 四倍精度 (16 字节)±3.36 × 10⁻⁴⁹³² ~ ±1.18 × 10⁴⁹³² 和 0.033~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,即使编译器支持它。

浮点数使用总结

记住以下两点:

  1. 浮点数适用于存储非常大或非常小、带小数部分的数值;
  2. 浮点数存在精度误差,甚至在有效数字少于精度限制时也可能出现舍入误差。

这些误差往往微小,不易察觉,尤其在输出时被自动截断。但它们确实存在,并且可能在比较和运算中造成问题。


关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"