【问题标题】:Comparing floating point number to zero将浮点数与零进行比较
【发布时间】:2013-11-19 04:40:45
【问题描述】:

C++ FAQ lite "[29.17] Why doesn't my floating-point comparison work?" 推荐这个相等测试:

#include <cmath>  /* for std::abs(double) */

inline bool isEqual(double x, double y)
{
  const double epsilon = /* some small number such as 1e-5 */;
  return std::abs(x - y) <= epsilon * std::abs(x);
  // see Knuth section 4.2.2 pages 217-218
}
  1. 是否正确,这意味着唯一等于零的数字是 +0-0
  2. 是否应该在测试零时也使用此功能,或者更确切地说是像|x| &lt; epsilon 这样的测试?

更新

正如 Daniel Daranas 所指出的,该函数可能最好称为 isNearlyEqual(我关心的是这种情况)。

有人指出"Comparing Floating Point Numbers",我想更突出地分享。

【问题讨论】:

  • 我脑子里有一句话说,永远不要测试双倍等于。只有更大或更小。
  • @user743414 在某些情况下,测试双精度等于完全没问题。例如。 if(counter &gt; 10.0) { counter = 0.0; //dostuff } 和代码中的其他地方:if(counter == 0.0){//oh I know that counter is reseted} else{//do other stuff}...
  • 你真正想做什么?至于问题 1,是的,唯一比较等于 +0.0(或实际上是 -0.0)的值是 +0.0 和 -0.0。但我没有看到问题中的代码暗示了这一点。
  • @relaxxx:计数器是整数。

标签: c++ floating-point


【解决方案1】:

您的观察是正确的。

如果x == 0.0,则abs(x) * epsilon 为零,您正在测试是否abs(y) &lt;= 0.0

如果y == 0.0,那么您正在测试abs(x) &lt;= abs(x) * epsilon,这意味着epsilon &gt;= 1(不是)或x == 0.0

所以is_equal(val, 0.0)is_equal(0.0, val) 都没有意义,你可以直接说val == 0.0。如果您只想完全接受+0.0-0.0

在这种情况下,FAQ 的建议是有限的。 没有“一刀切”的浮点比较。您必须考虑变量的语义、可接受的值范围以及计算引入的误差幅度。甚至常见问题解答都提到了一个警告,说这个函数通常不是问题,“当 x 和 y 的大小明显大于 epsilon 时,但你的里程可能会有所不同”。

【讨论】:

    【解决方案2】:

    没有。

    平等就是平等。

    正如它的名字所承诺的那样,您编写的函数不会测试两个双精度数是否相等。它只会测试两个双打是否“足够接近”。

    如果您真的想要测试两个双打是否相等,请使用这个:

    inline bool isEqual(double x, double y)
    {
       return x == y;
    }
    

    编码标准通常建议不要比较两个双精度数是否完全相等。但这是一个不同的主题。如果您实际上想要比较两个双精度值是否完全相等,x == y 就是您想要的代码。

    10.000000000000001 不等于 10.0,不管他们告诉你什么。

    使用完全相等的示例是当 double 的特定值用作某些特殊状态的同义词时,例如“等待计算”或“无可用数据”。仅当待定计算之后的实际数值只是双精度可能值的子集时,才有可能。最典型的特殊情况是当该值是非负数时,您使用 -1.0 作为“未决计算”或“无可用数据”的(精确)表示。你可以用一个常数来表示:

    const double NO_DATA = -1.0;
    
    double myData = getSomeDataWhichIsAlwaysNonNegative(someParameters);
    
    if (myData != NO_DATA)
    {
        ...
    }
    

    【讨论】:

    • 你的 isEqual 函数总有一天会让你大吃一惊 :) 可能当你意识到浮点数有多精确时。
    • @BЈовић 不。这不会让我感到惊讶。我很少使用它,但当我这样做时,我会检查一个双精度值是否等于另一个。 10.00000000000000001 不等于 10.0。
    • 我想最终的问题是人们在他们真正想要的东西被固定时使用浮动精度。 Daniel 的论点(10.00000000000000001 不是 10.0)是公平的,只是人们在比较“5.0 + 5.0”与“15.0 - 5.0”时并不认为这是一个因素。
    • 丹尼尔论点的问题在于,一旦我将 10.00000000000000001 存储为浮点数,它也不是 10.00000000000000001 。浮点数并不精确,它们是近似值。
    • @user3698909 这与我的论点并不矛盾。我的论点是相等的值就是 equal values 。如果您想知道两个浮点值是否相等,请测试它们是否相等。如果您对此不感兴趣,请不要测试它们是否相等 - 测试它们是否足够接近。
    【解决方案3】:

    如果您只对+0.0-0.0 感兴趣,可以使用&lt;cmath&gt; 中的fpclassify。例如:

    if( FP_ZERO == fpclassify(x) ) do_something;

    【讨论】:

      【解决方案4】:

      您可以将std::nextafterepsilon 的固定factor 一起使用,其值如下:

      bool isNearlyEqual(double a, double b)
      {
        int factor = /* a fixed 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;
      }
      

      【讨论】:

        【解决方案5】:

        2 + 2 = 5(*)

        (对于一些浮点精度值 2)

        当我们将“浮点”视为一种提高精度的方法时,经常会出现这个问题。然后我们与“浮动”部分发生冲突,这意味着无法保证可以表示哪些数字

        因此,虽然我们可以很容易地表示“1.0、-1.0、0.1、-0.1”,但当我们得到更大的数字时,我们开始看到近似值 - 或者我们应该看到近似值,除非我们经常通过截断数字来隐藏它们以显示.

        因此,我们可能认为计算机正在存储“0.003”,但它可能会存储“0.0033333333334”。

        如果执行“0.0003 - 0.0002”会发生什么?我们期望 .0001,但实际存储的值可能更像“0.00033” - “0.00029”,产生“0.000004”,或最接近的可表示值,可能是 0,也可能是“0.000006”。

        使用当前的浮点数学运算,it is not guaranteed that (a / b) * b == a

        #include <stdio.h>
        
        // defeat inline optimizations of 'a / b * b' to 'a'
        extern double bodge(int base, int divisor) {
            return static_cast<double>(base) / static_cast<double>(divisor);
        }
        
        int main() {
            int errors = 0;
            for (int b = 1; b < 100; ++b) {
                for (int d = 1; d < 100; ++d) {
                    // b / d * d ... should == b
                    double res = bodge(b, d) * static_cast<double>(d);
                    // but it doesn't always
                    if (res != static_cast<double>(b))
                        ++errors;
                }
            }
            printf("errors: %d\n", errors);
        }
        

        ideone 报告了 599 个实例,其中 (b * d) / d != b 仅使用 1

        FAQ 中描述的解决方案本质上是应用粒度约束——测试if (a == b +/- epsilon)

        另一种方法是通过使用定点精度或使用所需的粒度作为存储的基本单位来完全避免该问题。例如。如果您希望以纳秒精度存储时间,请使用纳秒作为存储单位。

        C++11 引入了std::ratio 作为不同时间单位之间定点转换的基础。

        【讨论】:

          【解决方案6】:

          就像@Exceptyon 指出的那样,此函数与您正在比较的值“相对”。 Epsilon * abs(x) 度量将基于 x 的值进行缩放,因此您将获得与 epsilon 一样准确的比较结果,而与 x 或 y 中的值范围无关。

          如果您将零 (y) 与另一个非常小的值 (x) 进行比较,例如 1e-8,abs(x-y) = 1e-8 仍将比 epsilon *abs(x) = 1e-13 大得多。因此,除非您正在处理无法以双精度类型表示的极小数字,否则此函数应该可以完成工作,并且只会与 +0-0 匹配零。

          该函数似乎完全适用于零比较。如果您打算使用它,我建议您在涉及浮点数的任何地方都使用它,并且对于诸如零之类的东西不要有特殊情况,以保持代码的一致性。

          ps: 这是一个简洁的功能。谢谢指点。

          【讨论】:

            【解决方案7】:

            FP数的简单比较有其特点,关键是对FP格式的理解(见https://en.wikipedia.org/wiki/IEEE_floating_point

            当 FP 数以不同的方式计算时,一种通过 sin(),另一种通过 exp(),严格相等将不起作用,即使数学上的数字可能相等。同样的方式不会与常数相等。实际上,在许多情况下,不能使用严格相等 (==) 来比较 FP 数

            在这种情况下应该使用 DBL_EPSIPON 常量,它是最小值不改变表示 1.0 被添加到超过 1.0 的数字。对于 2.0 以上 DBL_EPSIPON 根本不存在的浮点数。同时,DBL_EPSILON 的指数为 -16,这意味着与 DBL_EPSILON 相比,指数为 -34 的所有数字都绝对相等。

            另外,请参阅example,为什么 10.0 == 10.0000000000000001

            比较 dwo 浮点数取决于这些数字性质,我们应该为它们计算 DBL_EPSILON,这对比较有意义。简单地说,我们应该将 DBL_EPSILON 乘以这些数字之一。他们中的哪一个?当然最大

            bool close_enough(double a, double b){
                if (fabs(a - b) <= DBL_EPSILON * std::fmax(fabs(a), fabs(b)))
                {
                    return true;
                }
                return false;
            }
            

            所有其他方式都会给你带来难以捕捉的不平等错误

            【讨论】:

            • 没有添加,因为尾数被整数部分使用。 10.00000000000000001 不能用双精度表示。
            【解决方案8】:

            考虑这个例子:

            bool isEqual = (23.42f == 23.42);
            

            isEqual 是什么?十分之九的人会说“当然是true”而十分之九的人会说错:https://rextester.com/RVL15906

            这是因为浮点数不是精确的数字表示。

            作为二进制数,它们甚至不能准确地表示所有可以准确表示为十进制数的数字。例如。虽然0.1 可以精确地表示为十进制数(它恰好是1 的第十部分),但它不能用浮点数表示,因为它是0.00011001100110011... 周期性的二进制。 0.1 是浮点数,1/3 是十进制数(0.33333... 是十进制数)

            结果是像0.3 + 0.6 这样的计算可能会导致0.89999999999999991,而不是0.9,尽管它很接近。因此测试0.1 + 0.2 - 0.3 == 0.0 可能会失败,因为计算结果可能不是0,尽管它会非常接近0

            == 是精确测试,对不精确数字执行精确测试通常意义不大。由于许多浮点计算包括舍入误差,您通常希望您的比较也允许小错误,这就是您发布的测试代码的全部内容。它不是测试“Is A 等于 B”,而是测试“Is A 非常接近 B”,因为非常接近通常是您可以从浮点计算中获得的最佳结果.

            【讨论】:

              【解决方案9】:

              注意,代码是:

              std::abs((x - y)/x) <= epsilon
              

              您要求 var 上的“相对误差”是

              【讨论】:

              • 只有当 x 不为 0 时才等效。
              猜你喜欢
              • 1970-01-01
              • 2021-05-28
              • 2017-08-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多