当前位置: 首页 > news >正文

浮点数比较的致命陷阱与正确解法(精度问题)

        浮点数在编程中的一个核心陷阱:由于二进制表示的限制,许多十进制小数无法在内存中被精确地表示

目录

一、问题根源:二进制表示

二、直接比较的风险

三、正确的比较方法:使用容差(Tolerance)

四、编程语言中的实现

五、更专业的做法:使用相对容差

六、总结与最佳实践


一、问题根源:二进制表示

        计算机使用二进制(基数为2)来存储所有数据。对于整数,二进制可以完美表示。但对于小数,情况就复杂了。
        许多在十进制中看起来非常简单的数(如 0.10.23.45),在二进制中却是无限循环小数,使用乘二取整法:

  1. 用小数部分乘以 2。

  2. 记录结果的整数部分(只能是 0 或 1),这将是二进制小数点后的一位。

  3. 取结果的小数部分,继续重复步骤 1 和 2。

  4. 直到小数部分为 0,或者达到所需的精度,或者发现循环 pattern。

  • 经典例子:0.1

    • 十进制 0.1 转换成二进制(乘2取整法)是一个无限循环序列:0.00011001100110011...

    • 这类似于在十进制中无法精确表示 1/30.33333...)。

  • 例子 3.45:十进制 3.45 的二进制表示同样也是无限循环的。因此,当它被存储到 float 或 double 这种有限位的变量中时,必然会被舍入(Round) 为一个近似的值。


二、直接比较的风险

        正因为存储的是近似值,所以直接使用 == 来比较两个浮点数是否相等是极其危险不推荐的做法。

float f = 3.45; // f 在内存中的值可能是 3.4499999 或 3.4500001 之类的近似值
if (f == 3.45) { // 这里的 3.45 默认是 double 类型,也会被近似存储// 这个条件很大概率不会为真,即使看起来它们“应该”相等
}

        上面的代码几乎永远不会进入 if 语句块,因为 f 和字面量 3.45 都只是它们真实值的近似,并且这两个近似值可能还有细微的差异。


三、正确的比较方法:使用容差(Tolerance)

        正确的做法是检查两个浮点数的差值是否在一个可接受的、极小的误差范围内。这个误差范围就是“容差”。

 (fabs(f - 3.45) < 0.0000001) 正是这种方法的完美实践。

  • fabs(): C/C++ 中的函数,用于计算一个浮点数的绝对值(f absolute value)。因为差值可能是正也可能是负,我们关心的是差的“大小”。

  • f - 3.45: 计算实际存储的近似值和目标值之间的差异。

  • < 0.0000001: 判断这个差异是否足够小,小到我们可以认为它们在逻辑上是“相等”的。这个容差值 (0.0000001,即 1e-7) 需要根据你的计算精度要求来选择。对于 float,常用 1e-7;对于 double,常用 1e-15


四、编程语言中的实现

        这种比较方法在所有语言中都是通用的思想,只是函数名可能不同。但是我们学习的是C/C++语言,所以就记住这个就行了,其他不用记住。

语言绝对值函数示例代码
C/C++fabs() (for doubles), fabsf() (for floats)if (fabs(a - b) < 1e-7) { /* equal */ }
JavaMath.abs()if (Math.abs(a - b) < 1e-7) { /* equal */ }
Pythonabs()if abs(a - b) < 1e-7: # equal
JavaScriptMath.abs()if (Math.abs(a - b) < 1e-7) { // equal }

五、更专业的做法:使用相对容差

        对于非常非常大或非常非常小的数字,固定的绝对容差(如 1e-7)可能不再适用。更健壮的方法是使用相对容差,它根据数值的大小来调整容差范围。

一个常见的相对容差比较公式是:

#include <math.h> // 需要包含 math.h 头文件// 同时考虑绝对容差和相对容差,更健壮
if (fabs(a - b) < 1e-7 + 1e-7 * fabs(b)) {// 认为 a 和 b 相等
}
// 或者先判断绝对值,如果非常接近0,就用绝对容差,否则用相对容差

六、总结与最佳实践

  1. 永远不要用 == 或 != 来直接比较浮点数。这是一个常见的初学者错误,会导致程序出现难以调试的逻辑bug。

  2. 始终使用容差比较。判断两个浮点数之差的绝对值是否小于一个预先定义的、极小的容差值(epsilon)。

  3. 容差值的选择

    • 绝对容差:对于靠近 0 的数或精度要求固定的情况适用。float 可用 1e-7double 可用 1e-15

    • 相对容差:对于数值范围波动很大的情况更健壮。

  4. 语言习惯:注意你使用的语言中,默认的浮点字面量是什么类型(如C++中 3.45 是 double,而 3.45f 是 float)。混合类型比较可能会引入额外的隐式转换误差,最好保持类型一致。

http://www.dtcms.com/a/346844.html

相关文章:

  • linux下的网络编程:基础概念+UDP编程
  • Class41样式迁移
  • 55.Redis搭建主从架构
  • 计算机网络 各版本TLS握手的详细过程
  • CSS学习步骤及详解
  • 美食菜谱数据集(13943条)收集 | 智能体知识库 | AI大模型训练
  • JUC之虚拟线程
  • ArcGIS Pro 安装路径避坑指南:从崩溃根源到规范实操(附问题修复方案)
  • 运行npm run命令报错“error:0308010C:digital envelope routines::unsupported”
  • 使用 AD 帐户从 ASP.NET 8 容器登录 SQL Server 的 Kerberos Sidecar
  • 【深入理解 Linux 网络】收包原理与内核实现(下)应用层读取与 epoll 实现
  • 5G物联网的现实与未来:CTO视角下的成本、风险与破局点
  • 嵌入式学习日记(33)TCP
  • OpenFeign相关记录
  • 【嵌入式】【搜集】RTOS相关技术信息整理
  • Ubuntu2204server系统安装postgresql14并配置密码远程连接
  • 【python与生活】如何自动总结视频并输出一段总结视频?
  • FastAPI + SQLAlchemy 数据库对象转字典
  • 【力扣 Hot100】每日一题
  • C++之list类的代码及其逻辑详解 (中)
  • Java线程的几种状态 以及synchronized和Lock造成的线程状态差异,一篇让你搞明白
  • Linux服务器Systemctl命令详细使用指南
  • GitLab CI:安全扫描双雄 SAST vs. Dependency Scanning 该如何抉择?
  • 智慧园区人车混行误检率↓78%!陌讯动态决策算法实战解析
  • html链接的target属性
  • Win11 下卸载 Oracle11g
  • 《文字的本体论突围:从工具论到符号学革命的范式转换》
  • B.30.01.1-Java并发编程及电商场景应用
  • 算法 ---哈希表
  • 从0到1:数据库进阶之路,解锁SQL与架构的奥秘