当前位置: 首页 > 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.xdnf.cn/news/1351243.html

相关文章:

  • 【Linux】深度学习Linux下的包管理器yum/apt
  • 自动化知识工作AI代理的工程与产品实现
  • 文献阅读笔记【物理信息神经网络】:Physics-informed neural networks: A deep learning framework...
  • 深入理解 Linux 系统文件 I/O:从 open 到重定向的底层逻辑》
  • CA6150主轴箱系统设计cad+设计说明书
  • Spring:IOC(控制反转 )、DI(依赖注入 )、AOP(通知类型、事务、拦截器)
  • 博士招生 | 美国圣地亚哥州立大学 Yifan Zhang 课题组博士招生,AI 安全领域顶尖平台等你加入!
  • ​崩坏世界观中的安全漏洞与哲学映射:从渗透测试视角解构虚拟秩序的脆弱性​
  • lanczso算法中的额外正交化代码解释
  • Linux问答题:分析和存储日志
  • Leetcode—931. 下降路径最小和【中等】
  • 告别静态网页:我用Firefly AI + Spline,构建次世代交互式Web体验
  • 同类软件对比(一):Visual Studio(IDE) VS Visual Studio Code
  • 支持电脑课程、游戏、会议、网课、直播录屏 多场景全能录屏工具
  • LeetCode 448.找到所有数组中消失的数字
  • Ubuntu通过 systemd 管理 gpt4free,需为其创建 g4f.service 文件,定义服务的启动、停止等操作(未实践)
  • 97. 小明逛公园,Floyd 算法,127. 骑士的攻击,A * 算法
  • SQL注入1----(sql注入原理)
  • csrf漏洞学习笔记
  • 【KO】前端面试三
  • RobotFramework介绍与使用
  • 改华为智能插座为mqtt本地控制
  • 计算机视觉工程师业务场景题:智能推荐视频封面
  • ros 消息类型与查阅相关内容
  • Redis面试精讲 Day 28:Redis云原生部署与Kubernetes集成
  • 鸿蒙中CPU活动分析:CPU分析
  • Java—— 动态代理
  • 【Linux网络编程】分布式Json-RPC框架 - 项目设计
  • UAD详解
  • BEVDepth