C语言浮点数相等判定
等价关系
按照离散数学的等价关系叙述,集合
- 反身性:
,自己等于自己 - 对称性: 如果
,则 ,我等于你,则你也等于我 - 传递性: 如果
且 ,则 ,我等于你且你等于他,则我等于他
这里考虑 IEEE754 中双精度浮点数(即 C 语言中的 double)相等判定,按照 C 语言中 ==
判定,则它确实是等价关系,注意 0.0 == -0.0
成立(虽然它们的二进制中符号位不同)
由于机器舍入误差的存在,浮点数使用 ==
作为相等判定条件无法满足现实中的要求。例如 0.1 + 0.2 == 0.3
是不成立的(它们在二进制中均为无限循环小数)。
因此一般用绝对误差判定:fabs(x - y) < eps
时认为“相等”,其中 eps 是一个指定的小数。显然它满足反身性和对称性,但不满足传递性,因此此判定不是等价关系:例如取 x = eps/2, y = 0, z = -eps/2。
但绝对误差判定存在另一个问题:
因此再引入相对误差的判定:fabs(x - y) < eps1 || fabs(x - y) < eps2 * fabs(x) || fabs(x - y) < eps2 * fabs(y)
,其中 eps1 和 eps2 均为指定的小数且一般相等。显然它满足反身性和对称性,但不满足传递性:例如
于是有如下判定的 C 代码(仅满足反身性和对称性),全篇最终代码
#include <math.h>
#include <float.h>
int fIsEqual(double x, double y)
{
double z = fabs(x - y);
return z < DBL_EPSILON * 10 || z < DBL_EPSILON * 10 * fabs(x) || z < DBL_EPSILON * 10 * fabs(y);
}
- 其中
DBL_EPSILON
是 1 与比 1 大的最小浮点数之间的差值,按照 IEEE754 标准值为 - 这里的 10 为经验值,用于误差累计后依然能判过去,另外
DBL_EPSILON * 10
一定会被编译器优化成一个常量 - 返回值用 int 不用 bool 是因为 C23 才定义 bool
- 上述判定存在一个问题,
, 时fIsEqual(x, y) = 1
然而fIsEqual(x - 1, y - 1) = 0
, 即对线性运算不封闭,这是可以接受的,因为浮点数本来就不满足结合率,加法本身就有大数吃小数的现象。再者仅用绝对误差作为判据也无法满足对线性运算封闭的性质。
牺牲对称性换来所谓性能提升是没有意义的
上述代码中需要做三次判定才能确定不相等,考虑到如果 fabs(x - y) < DBL_EPSILON * 10 * fabs(y)
时, fabs(x)
与 fabs(y)
差距较小,再判断 fabs(x - y) < DBL_EPSILON * 10 * fabs(x)
有损性能且意义不大。于是删掉中间的判定条件,并用 x 的值覆盖 z,就推出下面所谓的"优化版"(仅满足反身性,不满足对称性和传递性,假优化请勿使用!!!)
#include <math.h>
#include <float.h>
int fIsEqual(double x, double y)
{
x = fabs(x - y);
return x < DBL_EPSILON * 10 || x < DBL_EPSILON * 10 * fabs(y);
}
但此时当 fabs(x - y) = 10
,此时 fIsEqual(x, y)
成立,但 fIsEqual(y, x)
不成立,这是难以让人接受的,详见下图
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战