74

C++ FAQ 精简版“[29.17] 为什么我的浮点比较不起作用?” 推荐这个平等测试:

#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

更新

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

有人指出“比较浮点数”,我想更突出地分享。

4

9 回答 9

54

你的观察是正确的。

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

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

所以要么is_equal(val, 0.0)oris_equal(0.0, val)都是没有意义的,你可以说val == 0.0. 如果您只想完全 +0.0接受and -0.0

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

于 2013-11-07T16:51:51.030 回答
20

不。

平等就是平等。

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

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

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)
{
    ...
}
于 2013-11-07T15:50:00.553 回答
13

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

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

于 2016-01-07T14:07:12.110 回答
6

您可以使用std::nextafter固定factorepsilon值,如下所示:

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;
}
于 2016-02-07T11:06:33.400 回答
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”。

对于当前的浮点数学运算,不能保证 (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 <= b <= 100 和 1 <= d <= 100 的 10,000 个组合。

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

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

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

于 2013-11-08T08:28:48.360 回答
2

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 相比绝对相等。

另外,请参阅示例,为什么 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;
}

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

于 2017-02-04T12:43:36.713 回答
1

就像@Exceptionon 指出的那样,这个函数与您正在比较的值是“相对的”。该Epsilon * abs(x)度量将根据 x 的值进行缩放,因此epsilon无论 x 或 y 的值范围如何,您都将获得与 一样准确的比较结果。

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

该函数对于零比较似乎完全有效。如果您打算使用它,我建议您在涉及浮点数的任何地方使用它,并且对于零之类的东西没有特殊情况,只是为了代码的一致性。

ps:这是一个简洁的功能。感谢您指出它。

于 2013-11-07T15:39:00.907 回答
1

考虑这个例子:

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

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

于 2020-06-09T15:22:02.803 回答
0

请注意,该代码是:

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

您要求 var 上的“相对误差”是 <= epsilon,而不是绝对差异是

于 2013-11-07T14:19:52.273 回答