《编写高质量代码:改善c程序代码的125个建议》——建议3-4:避免直接在浮点数中使用“==”操作符做相等判断

简介:

本节书摘来自华章计算机《编写高质量代码:改善c程序代码的125个建议》一书中的第1章,建议3-4,作者:马 伟 更多章节内容可以访问云栖社区“华章计算机”公众号查看。

建议3-4:避免直接在浮点数中使用“==”操作符做相等判断

在整型数据中,我们一般都使用“==”操作符来判断两个数是否相等。在浮点数据的运算中,也存在着“==”操作符,那么是否也可以使用这个“==”操作符来判断两个浮点数是否相等呢?带着这个问题,示例程序如代码清单1-21所示。

代码清单1-21 浮点数相等判断示例
#include <stdio.h>
int main(void)
{ 
    float f1=3.46f;
    float f2=5.77f;
    float f3=9.23f; 
    printf("f1(3.46f)=%0.20f\nf2(5.77f)=%0.20f\nf1+f2=%0.20f\n
            f3(9.23f)=%0.20f\n",f1,f2,f1+f2,f3);
    if(f1+f2==f3)
    {
            printf("f1+f2==f3\n");
    }
    else
    {
            printf("f1+f2!=f3\n");
    }       
    return 0;   
}

在代码清单1-21中,分别定义了3个float变量 f1、f2与f3。从表面上看,f1+f2的值应该是9.23,因此执行条件判断语句“if(f1+f2==f3)”时,应该返回true。但实际的运行结果并非如此,如图1-33所示。
要想使语句“if(f1+f2==f3)”返回true,那么f1+f2和f3在浮点格式的精度限制内必须严格相等。这也就意味着,一般情形下(0除外),浮点格式中的每一个位都必须相等。


b9f16248714e2a331539a1bf923e62f531b18675

由于浮点数存在误差,即使是同一意义上的值,如果来源不同,那么判断也就可能不会为true。换句话说,在浮点计算中,“==”的作用是比较两个浮点数是否具有完全相同的格式数据,而不是一般数学或工程意义上的相等。在浮点计算中两个数据相等的含义通常是指在误差范围内,两个数据的意义一致(即二者描述的物理量的取值一致,或者说相容),因此不能使用“==”操作符进行判断。
既然不能使用“==”操作符进行判断,那么我们又应该怎样正确判断两个浮点数是否相等呢?
一般情况下,浮点数的相等判断通常使用如下形式,即:


8c78c835440f5c8afb7fd39c165c0204b1e76f5b

示例代码如下所示:

if(fabs(a-b) < epsilon)

其中,epsilon是一个绝对的数据。采用这种形式来判断相等,很显然,如何确定Δ就成了问题的关键所在。Δ值的确定需要考虑数值背后的含义,而且它总是与误差的概念相随。
(1)依据数据误差进行判断
如果两个数据相差Δ,假设一个数据的误差是Δ1,另一个数据的误差是Δ2,那么一个简单的判据是:


<a href=https://yqfile.alicdn.com/0eededb373e73e53645f661c965e1b6292987982.png" >

实际上,如果数据不是直接来自某个测量设备,而是某个仿真系统的输出或者是测量数据经过一系列处理的结果,那么Δ1和Δ2大多没有确定的值。此外,这种方法在理论上也不够严谨,只是便于使用而已。
(2)依据允许误差进行判断
在许多情况下,计算精度和数据精度均远远超过了实际需求,使用数据误差进行相等判断除了加大计算量之外,没有实际意义。此时,则可根据实际精度需求确定允许误差,然后用允许误差替代数据误差进行相等判断。这种方法更简单,而且允许误差一般远大于数据误差,可以减小计算量。不过,所谓的允许误差往往没有确定的值,主要依据经验来判断,因此有较大的不确定性。
虽然相对于“==”操作符,使用if(fabs(a-b) < epsilon)形式进行判断是一个比较好的解决方案,但它却存在着一定的局限性。比如,epsilon的取值为0.0001,而a和b的数值大小也在0.0001附近,那么它显然是不合适的。另外,对于a和b大小是10000这样的数据,它也不合适,因为10000和10001也可以认为是相等的。
既然这种绝对误差形式“if(fabs(a-b) < epsilon)”存在着局限性,那么我们可以尝试使用相对误差的形式“fabs ( (a-b)/a ) < epsilon”进行判断,示例代码如下所示:
bool IsEqual(float a, float b, float epsilon ) 
{   
    return ( fabs ( (a-b)/a ) < epsilon ) ? true  : false; 
}

这样的判断形式看起来是可行的,但它同样存在着局限性。因为它是拿固定的第一个参数做比较的,如果我们分别调用IsEqual(a, b, epsilon)和IsEqual(b, a, epsilon),那么可能会得到不同的结果。与此同时,如果第一个参数是0,很可能会产生除0溢出。因此,我们可以把上面的判断形式改造为:除数选取为a和b当中绝对数值较大的即可,示例代码如下所示:

bool IsEqual(float a, float b, float epsilon )
{ 
    if (fabs(a)>fabs(b)) 
    {
            return  ( fabs((a-b)/a) < epsilon ) ? true : false;
    }
    else
    {
            return  (fabs( (a-b)/b) < epsilon ) ? true : false;
    }
}

这样看起来就更加完善了。当然,在某些特殊的情况下,相对误差也不能代表全部。因此,我们还需要将相对误差和绝对误差结合使用。完整的比较示例代码如下所示:

bool IsEqual(float a, float b, float epsilon ) 
{ 
    if (a==b) 
    {
            return true; 
    }
    if (fabs(a-b)<epsilon )
    {
            return true; 
    }
    if (fabs(a)>fabs(b)) 
    {
            return  ( fabs((a-b)/a) < epsilon ) ? true : false;
    }
    else
    {
            return  (fabs( (a-b)/b) < epsilon ) ? true : false;
    }
}
相关文章
|
存储 SQL JSON
【新闻推荐系统】(task2)MongoDB篇
本文属于新闻推荐实战—数据层—构建物料池之MongoDB。MongoDB数据库在该项目中会用来存储画像数据(用户画像、新闻画像),使用MongoDB存储画像的一个主要原因就是方便扩展,因为画像内容可能会随着产品的不断发展而不断的更新。作为算法工程师需要了解常用的MongoDB语法(比如增删改查,排序等),因为在实际的工作可能会从MongoDB中获取用户、新闻画像来构造相关特征。
846 0
【新闻推荐系统】(task2)MongoDB篇
|
6月前
|
Ubuntu 应用服务中间件 网络安全
关于一些轻量云服务器SSH断连的疑问
在使用2H2G配置的轻量级Ubuntu 22.04服务器时,按照Solana官网教程安装环境,执行`[cargo install]`命令(特别是安装avm和anchor包时),出现SSH连接中断且无法重新登录的问题。推测可能是低配服务器资源耗尽导致SSH进程被终止,即便CPU使用率下降也无法恢复连接,需重启服务器并等待约30分钟才能恢复正常。此现象或与服务器性能限制有关,期待更多测试与解释。
|
弹性计算 网络协议 安全
安全组规则
安全组规则
446 3
|
11月前
|
XML 移动开发 开发者
京东商品详情数据接口(H5、APP 端)
京东商品详情数据接口是为H5和APP开发者提供的工具,支持获取商品名称、价格、库存、销量、评价、图片等详细信息,优化应用功能。接口返回JSON或XML格式数据,方便解析处理。适用于电商导购、社交媒体分享、活动推广、价格监控等场景,提升用户体验和购物决策效率。示例代码展示了使用Python发送GET请求的方法。
|
数据可视化 前端开发 JavaScript
Echarts+JS实现农业指挥舱可视化大屏!!附源码!!
Echarts+JS实现农业指挥舱可视化大屏!!附源码!!
|
JSON 小程序 JavaScript
超详细微信小程序开发学习笔记,看完你也可以动手做微信小程序项目
这篇文章是一份全面的微信小程序开发学习笔记,涵盖了从小程序介绍、环境搭建、项目创建、开发者工具使用、文件结构、配置文件、模板语法、事件绑定、样式规范、组件使用、自定义组件开发到小程序生命周期管理等多个方面的详细教程和指南。
|
数据处理 Apache 流计算
【Flink】Flink 中的Watermark机制
【4月更文挑战第21天】【Flink】Flink 中的Watermark机制
|
计算机视觉 Python
OpenCV(二十):图像卷积
OpenCV(二十):图像卷积
313 0
|
NoSQL 算法 Java
高频面试题实现分布式锁中,zookeeper和Redis哪种更好?
一位6年工作经验的小伙伴,在某厂面试时被问到“实现分布式锁,Zookeeper 和 Redis 哪种更好?“,这其实是一个开放性的问题。并没有标准答案。那今天呢,我给大家分享一下我的理解,希望能够帮助到大家。
477 0
高频面试题实现分布式锁中,zookeeper和Redis哪种更好?
|
C++
QT图形视图系统 - 使用一个项目来学习QT的图形视图框架 - 始篇
详细的介绍可以看QT的官方助手,那里面介绍的详细且明白,需要一定的英语基础,我这里直接使用一个开源项目来介绍QGraphicsView、QGraphicsScene的使用。
654 1