将浮点数与零进行比较

时间:2013-11-07 13:46:37

标签: c++ floating-point

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| < epsilon
  3. 这样的测试时,是否也应该使用此功能?

    更新

    正如丹尼尔·达拉纳斯所指出的那样,函数应该更好地称为isNearlyEqual(这是我关心的情况)。

    有人指出this link,我想更加突出地分享。

9 个答案:

答案 0 :(得分:38)

你的观察是正确的。

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

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

因此,is_equal(val, 0.0)is_equal(0.0, val)无意义,您可以说val == 0.0。如果您只想接受完全 +0.0-0.0

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

答案 1 :(得分:18)

没有

平等是平等。

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

如果确实想要测试两个双打的相等性,请使用以下内容:

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

编码标准通常建议不要将两个双精度数进行比较以确保相等。但那是一个不同的主题。如果实际上想要比较两个双精度数以获得完全相等,x == y就是您想要的代码。

10.00000000000000001不等于10.0,无论他们告诉你什么。

使用精确相等的示例是指将double的特定值用作某些特殊状态的同义词,例如“pending calulation”或“no data available”。仅当挂起计算之后的实际数值仅是double的可能值的子集时,才可以执行此操作。最典型的特殊情况是该值为非负值,并使用-1.0作为“待定计算”或“无可用数据”的(精确)表示。你可以用常数表示:

const double NO_DATA = -1.0;

double myData = getSomeDataWhichIsAlwaysNonNegative(someParameters);

if (myData != NO_DATA)
{
    ...
}

答案 2 :(得分:6)

您可以将std::nextafterfactor的{​​{1}}值固定为epsilon,如下所示:

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;
}

答案 3 :(得分:5)

如果您只对+0.0-0.0感兴趣,可以使用<cmath>中的fpclassify。例如:

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

答案 4 :(得分:4)

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&lt; = b&lt; = 100且1&lt; = d&lt; = 100的10,000个组合。

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

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

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

答案 5 :(得分:1)

就像@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: 这是一个很好的功能。谢谢你的指点。

答案 6 :(得分:1)

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,这意味着所有数字,比如指数为-34,与DBL_EPSILON相比绝对相等。

另请参阅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;
}

所有其他方式都会给你带来不平等的错误,这可能很难捕捉

答案 7 :(得分:0)

注意,该代码是:

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

你要求var上的“相对误差”是&lt; = epsilon,而不是绝对差值是

答案 8 :(得分:0)

考虑以下示例:

bool isEqual = (23.42f == 23.42);

什么是isEqual?每10个人中就有9个人会说“ 当然是true ”,而每10个人中就有9个人说错了: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非常接近。

==是精确测试,对不精确的数字执行精确测试通常没有什么意义。由于许多浮点计算都包含舍入误差,因此您通常希望比较结果也允许出现小的误差,这就是您发布的测试代码的全部目的。而不是测试“ 是否等于B ”,而是测试“ 是否等于B ”,因为非常接近通常是您从浮点计算中可以预期的最佳结果