【问题标题】:How to correctly and standardly compare floats?如何正确和标准地比较浮点数?
【发布时间】:2011-05-31 16:27:08
【问题描述】:

每次我开始一个新项目并且需要比较一些浮点或双精度变量时,我都会编写如下代码:

if (fabs(prev.min[i] - cur->min[i]) < 0.000001 &&
    fabs(prev.max[i] - cur->max[i]) < 0.000001) {
        continue;
}

然后我想摆脱这些神奇的变量 0.000001(和 0.00000000001 对于 double)和 fabs,所以我编写了一个内联函数和一些定义:

#define FLOAT_TOL 0.000001

所以我想知道是否有任何标准方法可以做到这一点?可能是一些标准的头文件? 有浮动和双重限制(最小值和最大值)也很好

【问题讨论】:

  • 取决于用例,但是非常小的数字呢?您的代码将比较 1e-101e-15-1e-10 都相等。没有单一的“正确”方法来比较浮点数的“接近性”。
  • 为什么是#define?您可以为此目的使用静态 const float。
  • 我敢打赌你忘了和0比较? :)

标签: c++ floating-point floating-accuracy double-precision


【解决方案1】:

来自The Floating-Point Guide

这是一种不好的方法,因为 选择固定 epsilon 是因为它“看起来 小”实际上可能太大了 当被比较的数字是 也非常小。比较 将返回“真”的数字 完全不同。而当 数字非常大,ε 最终可能会小于 舍入误差最小,因此 比较总是返回“false”。

这里“幻数”的问题不在于它是硬编码的,而在于它是“幻数”:您真的没有理由选择 0.000001 而不是 0.000005 或 0.0000000000001,是吗?请注意,float 可以近似地表示后者和更小的值 - 它只是在第一个非零数字之后 大约 7 位精度!

如果您要使用固定的 epsilon,您应该根据使用它的特定代码段的要求来选择它。另一种方法是使用相对误差范围(有关详细信息,请参阅顶部的链接),或者更好,或者compare the floats as integers

【讨论】:

  • 对于它的价值,Bruce Dawson 提到他关于比较浮点数的文章现在已经过时,读者应该参考the 2012 edition
  • @Chris Frederick:谢谢,我会在网站上添加指向该版本的链接
【解决方案2】:

标准提供了一个 epsilon 值。它在&lt;limits&gt; 中,您可以通过std::numeric_limits&lt;float&gt;::epsilonstd::numeric_limits&lt;double&gt;::epsilon 访问该值。里面还有其他值,但我没有检查到底是什么。

【讨论】:

  • 虽然请注意epsilon 不是提问者使用的恒定容差的直接替代品。它表示值 1.0 的最低有效位中的 out-by-1,因此如果您的值大约为 2,那么它太小而无法提供任何容差。很难有效使用。
【解决方案3】:

您可以使用std::nextafter 来测试两个double,在一个值上具有最小的epsilon(或最小epsilon 的因子)。

bool nearly_equal(double a, double b)
{
  return std::nextafter(a, std::numeric_limits<double>::lowest()) <= b
    && std::nextafter(a, std::numeric_limits<double>::max()) >= b;
}

bool nearly_equal(double a, double b, int factor /* a factor of epsilon */)
{
  double min_a = a - (a - std::nextafter(a, std::numeric_limits<double>::lowest())) * factor;
  double max_a = a + (std::nextafter(a, std::numeric_limits<double>::max()) - a) * factor;

  return min_a <= b && max_a >= b;
}

【讨论】:

    【解决方案4】:

    感谢您的回答,他们对我帮助很大。我已经阅读了这些材料:firstsecond

    答案是使用我自己的函数进行相对比较:

    bool areEqualRel(float a, float b, float epsilon) {
        return (fabs(a - b) <= epsilon * std::max(fabs(a), fabs(b)));
    }
    

    这是最适合我需求的解决方案。但是我写了一些测试和其他比较方法。我希望这对某人有用。 areEqualRel 通过了这些测试,其他的则没有。

    #include <iostream>
    #include <limits>
    #include <algorithm>
    
    using std::cout;
    using std::max;
    
    bool areEqualAbs(float a, float b, float epsilon) {
        return (fabs(a - b) <= epsilon);
    }
    
    bool areEqual(float a, float b, float epsilon) {
        return (fabs(a - b) <= epsilon * std::max(1.0f, std::max(a, b)));
    }
    
    bool areEqualRel(float a, float b, float epsilon) {
        return (fabs(a - b) <= epsilon * std::max(fabs(a), fabs(b)));
    }
    
    int main(int argc, char *argv[])
    {
        cout << "minimum: " << FLT_MIN      << "\n";
        cout << "maximum: " << FLT_MAX      << "\n";
        cout << "epsilon: " << FLT_EPSILON  << "\n";
    
        float a = 0.0000001f;
        float b = 0.0000002f;
        if (areEqualRel(a, b, FLT_EPSILON)) {
            cout << "are equal a: " << a << " b: " << b << "\n";
        }
        a = 1000001.f;
        b = 1000002.f;
        if (areEqualRel(a, b, FLT_EPSILON)) {
            cout << "are equal a: " << a << " b: " << b << "\n";
        }
    }
    

    【讨论】:

    • 当然你的意思是 std::max(fabs(a), fabs(b)),除非你所有的浮点数都是正数
    • 谢谢TonyK,你说得对,我没时间写一个完整的单元测试看看。我已经在我的帖子中修复了 areEqualRel
    • 不错,但可能不适用于像次正规数这样的特殊浮点数。
    【解决方案5】:

    你应该知道,如果你比较两个浮点数是否相等,你 本质上是在做错事。在比较中添加倾斜因子 不够好。

    【讨论】:

    • 同意@ddyer:OP 需要去上一门数值分析课程。
    • 单元测试怎么样?如果我正在测试一种算法,并且我想检查给定输入值的结果是否接近预期(浮点)值?
    • 好问题,没有简单的答案。如果您只是检查算法中的严重错误,那么我认为倾斜因子是一个不错的起点。其他测试将涉及提供旨在触发问题的数据,例如使用 2^32-1 作为整数输入。更一般地说,您可能会绘制出您的实施与参考标准之间的差异,以寻找差异的证据。
    • 这仅适用于部分用例。在我目前的情况下,正在进行浮点比较,以消除来自 A2D 的值,并选择一个 epsilon 来反映 A2D 的属性。
    【解决方案6】:

    您应该使用 float.h 中的标准定义:

    #define DBL_EPSILON     2.2204460492503131e-016 /* smallest float value such that 1.0+DBL_EPSILON != 1.0 */
    

    或 numeric_limits 类:

    // excerpt
    template<>
    class numeric_limits<float> : public _Num_float_base
    {
    public:
        typedef float T;
    
        // return minimum value
        static T (min)() throw();
    
        // return smallest effective increment from 1.0
        static T epsilon() throw();
    
        // return largest rounding error
        static T round_error() throw();
    
        // return minimum denormalized value
         static T denorm_min() throw();
    };
    

    [编辑:让它更具可读性。]

    但除此之外,这取决于你所追求的。

    【讨论】:

    • +1:很好,但标题复制粘贴并不是最有用的 IMO。
    • 我只是想表明 numeric_limits 实现中还有更多有趣的值。
    • 您从哪里得到“1.0+DBL_EPSILON != 1.0 的最小浮点值”的评论?这是定义DBL_EPSILON 的错误短语。 blog.frama-c.com/index.php?post/2013/05/09/FLT_EPSILON
    • 我从 Visual Studio 2012 的实现中得到了评论。直到你的帖子才想到。
    【解决方案7】:

    这是 @geotavros 解决方案的 c++11 实现。它利用了新的std::numeric_limits&lt;T&gt;::epsilon() 函数以及std::fabs()std::fmax() 现在具有floatdoublelong float 的重载这一事实。

    template<typename T>
    static bool AreEqual(T f1, T f2) { 
      return (std::fabs(f1 - f2) <= std::numeric_limits<T>::epsilon() * std::fmax(std::fabs(f1), std::fabs(f2)));
    }
    

    【讨论】:

      【解决方案8】:

      这篇文章对如何比较浮点数进行了全面的解释: http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/

      摘录:

      • 如果您要与零进行比较,那么基于相对 epsilon 和 ULP 的比较通常是没有意义的。您需要使用 绝对 epsilon,其值可能是 FLT_EPSILON 和计算的输入。也许吧。
      • 如果您要与非零数进行比较,那么基于相对 epsilon 或 ULP 的比较可能是您想要的。你会 可能想要您的亲戚使用 FLT_EPSILON 的小倍数 epsilon 或一些少量的 ULP。一个绝对的 epsilon 可能是 如果您确切知道要比较的数字,则使用该数字。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-02-22
        • 2012-05-13
        • 1970-01-01
        • 2015-01-20
        • 1970-01-01
        相关资源
        最近更新 更多