比较浮点数和零的标准方法是什么?

6
我的问题是:如何标准地比较浮点数和零?
据我所知,直接比较会存在精度问题:
if ( x == 0 ) { 
  // x is zero?
} else {
  // x is not zero??

浮点变量可能会失败。

我曾经使用过

float x = ...
...
if ( std::abs(x) <= 1e-7f ) { 
  // x is zero, do the job1
} else {
 // x is not zero, do the job2
...

我发现这里也有同样的方法。但是我发现两个问题:

  1. 存在随机的魔数 1e-7f ( 或者在上述链接中为 0.00005 )。
  2. 代码难以阅读

这种比较很常见,我想知道是否有标准的简短方式来做到这一点。例如

 x.is_zero();

1
你的使用场景是什么?为什么要将浮点数与零进行比较? - David Schwartz
6
没有标准,因为 epsilon 的适当值取决于具体应用。 - Barmar
这取决于你想做什么。这个数字是从哪里来的?如果它是零,为什么很重要?是否存在低于 job1 无法工作的限制? - Alan Stokes
@David Schwartz,我有很多用例,并且厌倦了这个总是有不同魔法数字的丑陋代码。例如,我需要知道是否需要旋转对象或旋转角度== 0,或者两个对象是否具有相同的X位置。 - klm123
4
@klm123 你能看出为什么这两种不同的用例需要不同的算法吗?事实上,这两种情况都应该与物体的大小有关。对于小物体来说,0.01度的旋转可以忽略不计,但如果是大物体,则不是这样。你必须仔细思考每种情况。 - David Schwartz
3个回答

7

要将浮点值与0进行比较,只需直接进行比较:

if (f == 0)
    // whatever

这个比较并没有问题。如果它不能实现你的预期目标,那么问题可能在于f的值不是你所想象的。这本质上和下面的问题是一样的:

int i = 1/3;
i *= 3;
if (i == 1)
    // whatever

这个比较并没有问题,但是变量i的值不是1。几乎所有程序员都理解整数值的精度损失,但很多人不理解浮点数值的精度损失。

使用“近似相等”而不是==是一种高级技巧,通常会导致意想不到的问题。例如,它不具有传递性;也就是说,a近似等于bb近似等于c不意味着a近似等于c


4
@Phlip:不,程序员应该了解使用浮点数时出现错误的原因,并设计具有适当容差的算法。 - Mike Seymour
3
@Phlip: 不,浮点数有明确定义的语义。因为懒得理解它而将浮点数视为“不可预测”并不是正确的做法。请努力理解它。 - tmyklebu
4
@Phlip - 你说得对,这个问题不像整数截断那么清楚明了(尽管对许多初学者来说,整数截断也不是显而易见的;可以看看StackExchange上有多少关于这个问题的提问,答案是“你正在进行整数运算;将其改为浮点数”)。然而,这本质上是相同的问题,解决方法是理解为什么你的数字不如你所期望的,并相应地修正你的期望。不幸的是,编程课程通常并不教授浮点数非常好,结果程序员们对此感到害怕。 - Pete Becker
3
@Phlip - 你有什么替代方案建议吗?如果有人不理解浮点数学,那么添加更多他们不理解的内容(比如“几乎相等”)会使情况变得更糟。如果你认为浮点数学是不可预测的,那意味着你对它的了解还不够,还不能使用它。在这种情况下,请不要使用它,直到你对它有更多的了解。 - Pete Becker
3
“除了溢出情况,整数算术是精确的”这并不是一个正确的说法。当将5%的年利率转换为月利率时,整数算术会得出错误的答案。即使没有溢出,整数算术也不是结合律。即使像一些Stack Overflow答案建议的那样,将美元缩放到便士,当一个产品价格乘以销售税率时,整数算术也会得出错误的四舍五入答案。 - Eric Postpischil
显示剩余14条评论

5
没有标准方法,因为是否将一个小数视为零取决于你如何计算该数字以及它的用途。这又取决于你的计算引入的任何误差的预期大小,以及确定你的原始输入的物理测量误差。
例如,假设你的值表示某个制图软件中的英里旅程的长度。那么,你可以将1e-7视为等于零,因为在这种情况下,它是一个非常小的数字:它是由于舍入误差或其他轻微不精确性的原因而产生的。
另一方面,假设你的值表示电子显微镜软件中分子的大小。那么,你肯定不希望将1e-7视为等于零,因为在这种情况下,它是一个非常大的数字。
你应该首先考虑什么是适当的精度来展示你的值:误差条,或者你可以合理显示多少有效数字。这将给你一些想法,你需要测试对零的容限,尽管它可能还没有解决问题。对于制图软件,如果旅程小于某个固定值,你可以将其视为零,尽管该值本身可能取决于地图的分辨率。对于显微镜软件,如果两个大小之间的差异使得零位于这些测量的95%误差范围内,这仍然可能不足以将它们描述为相同的大小。

4

我不知道我的回答是否有用,我在irrlicht的irrmath.h中找到了这个函数,并且一直在引擎的mathlib中使用它:

const float ROUNDING_ERROR_f32 = 0.000001f;

//! returns if a equals b, taking possible rounding errors into account
inline bool equals(const float a, const float b, const float tolerance = ROUNDING_ERROR_f32)
{
        return (a + tolerance >= b) && (a - tolerance <= b);
}

作者解释了这种方法是“经过许多旋转,即三角函数操作后,坐标会失真,直接比较可能会导致错误”。

这正是我使用的,看帖子。 - klm123
@klm123 我认为这里的重点是 tolerance 参数,你通常应该提供它。我认为为其设置一个数字常量默认值略显低效,我要么不设置默认值,要么使用哨兵值,然后设置可修改的默认值(在给定哨兵值时使用)。 - hyde

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接