应使用容差比较而非==判断浮点数相等,因浮点数是二进制近似表示,直接比较易因微小误差失败;推荐采用相对误差加绝对误差的组合判断方式。

直接用 == 比较两个 float 或 double 几乎总是错的
浮点数在内存中是二进制近似表示,像 0.1 这种十进制小数根本无法精确存储。所以 a == b 看似相等,实际可能差了 1e-16(double)或 1e-7(float),结果就是 false。
常见错误现象:
- 单元测试里 assert(a == 0.3) 失败,尽管你写了 a = 0.1 + 0.2
- 循环里用 for (double x = 0.0; x != 1.0; x += 0.1) 变成死循环
- GUI 滑块值比较、物理引擎碰撞检测失效
- 永远别用
==或!=判断浮点数逻辑相等 - “相等”在浮点语境下只能是“足够接近”,即引入容差(epsilon)
- 容差不能硬写成固定值(比如
1e-9),得看量级:比较1e20和1e20+1时,1e-9完全没意义
用相对误差 + 绝对误差组合判断(推荐通用写法)
单一绝对容差(abs(a - b) )在数值很小时有效,但大数时太宽松;单一相对容差(<code>abs(a - b) / max(abs(a), abs(b)) )在接近零时会除零或失真。稳妥做法是两者结合。
实操建议:
- 定义一个辅助函数,例如:
bool float_equal(double a, double b, double abs_eps = 1e-9, double rel_eps = 1e-12) { double diff = std::abs(a - b); double scale = std::max({std::abs(a), std::abs(b), 1.0}); return diff <= abs_eps || diff <= rel_eps * scale; } -
abs_eps应对接近零的情况(比如-1e-10和1e-11),rel_eps控制相对精度(通常1e-12对double足够) - 注意
scale分母取std::max(..., 1.0)是为避免a和b都极小(如1e-20)时,相对误差被放大到虚假超限 - 如果明确知道数量级(比如坐标都在
[0, 100]),可简化为纯绝对误差,但需注释说明依据
std::numeric_limits<T>::epsilon() 不是万能容差
std::numeric_limits<double>::epsilon() 返回的是 1.0 附近的单位精度(约 2.2e-16),它**不是**任意两数比较的通用阈值。
立即学习“C++免费学习笔记(深入)”;
- 它只代表
1.0和下一个可表示double的差值,数值越大,相邻浮点数间距(ULP)也越大 - 直接写
abs(a - b) 仅在 <code>a和b都接近1.0时勉强可用 - 更安全的用法是:把 epsilon 换算成当前数量级下的 ULP 容差,例如
abs(a - b) —— 这其实就回到了上一节的相对误差思路 - 某些库(如 Google Test)的
ASSERT_DOUBLE_EQ内部用的就是带缩放的 epsilon,不是裸 epsilon
特殊值(NaN、Inf)必须显式处理
NaN 和 Inf 是浮点标准的一部分,但它们不遵守常规比较规则:NaN == NaN 是 false,Inf == Inf 是 true,但你很可能不希望 NaN 被当成“相等”混过去。
- 若业务逻辑中可能出现
NaN(比如未初始化变量、除零、无效数学运算),必须先用std::isnan()检查 - 典型防御写法:
if (std::isnan(a) || std::isnan(b)) return false; // 或按需抛异常
-
std::isinf()同理:若允许无穷大且认为+Inf == +Inf合理,可保留;否则也应单独判断 - 很多团队会在调试构建中加断言:
assert(!std::isnan(x) && !std::isinf(x)),防止问题扩散
真正麻烦的从来不是怎么写判断,而是忘记检查输入是否合法、没想清楚“相等”在当前业务里到底意味着什么——比如两个向量角度差小于 0.01 弧度算相等,和两个质量值差小于 1e-15 千克算相等,容差来源完全不同。










