如何比较catch2中的浮点数

How to compare floating point in catch2

我正在使用 Catch v2.13.1

比较浮点值的正确方法是什么。我以为下面会失败,但都通过了。

REQUIRE(1147332687.7189338 == Approx(1147332688.4281545).margin(0.0001));
REQUIRE(1147332687.7189338 == Approx(1147332688.4281545));

然而这如预期的那样失败了

REQUIRE(abs(1147332687.7189338 - 1147332688.4281545) <= Approx(0).margin(0.0001));

我不明白为什么前两个语句不起作用

我想我找到了为什么会这样,但我不知道如何让它在 Catch 中工作。

This:

#include <iostream>
#include <limits>
#include <iomanip>

int main()
{
    double a = 1147332687.7189338;
    double b = 1147332688.4281545;
    float c = 1147332687.7189338;
    float d = 1147332688.4281545;

    std::cout << std::setprecision(std::numeric_limits<decltype(a)>::max_digits10) << a << std::endl;
    std::cout << std::setprecision(std::numeric_limits<decltype(b)>::max_digits10) << b << std::endl;
    std::cout << std::setprecision(std::numeric_limits<decltype(c)>::max_digits10) << c << std::endl;
    std::cout << std::setprecision(std::numeric_limits<decltype(d)>::max_digits10) << d << std::endl;
}

输出这个:

1147332687.7189338
1147332688.4281545
1.14733274e+09
1.14733274e+09

很明显,浮点数不足以区分这些数字,但双精度数可以。似乎 Catch 内部使用了浮点数;即使我试图强制它使用双打,测试也会通过。但也许我错过了什么?

TEST_CASE("Test1")
{
    double d1 = 1147332687.7189338;
    double d2 = 1147332688.4281545;
    REQUIRE(d1 == Approx(d2));
}

所以没有修复,但至少你现在知道为什么会得到那些奇怪的结果。

发布的示例中有几件事需要考虑。

REQUIRE(1147332687.7189338 == Approx(1147332688.4281545));

这会过去,“出乎意料”。原因可以在文档中找到(assertions - floating point comparisons)。

Approx is constructed with defaults that should cover most simple cases. For the more complex cases, Approx provides 3 customization points:

  • epsilon - epsilon serves to set the coefficient by which a result can differ from Approx's value before it is rejected. By default set to std::numeric_limits<float>::epsilon()*100.
  • [...]

在发布的示例中,这两个数字相差接近 6.2e-10 的系数,而默认值(给定 32 位浮点数)接近 1.2e-5。

下面的测试不会通过。

CHECK( a == Approx(b).epsilon(1e-12) );

其他测试涉及 margin,在文档中描述为

  • margin - margin serves to set the the absolute value by which a result can differ from Approx's value before it is rejected. By default set to 0.0.

不过,可以在 issue#1507 中找到警告。

This is because of the default value for epsilon in the Approx class and the fact that Approx::equalityComparisonImpl will pass if the value is in the range of the margin OR the epsilon values.

所以,这个测试不会通过:

CHECK( a == Approx(b).margin(0.0001).epsilon(1e-12) );

请注意,这个“问题”似乎被标记为 resolved - 不是错误:

So, I don't think this is a bug.

The reason for this is that it is very hard (well, impossible), to determine the user's intent, thus it is better to assume that the user has set up both checks correctly -- after all, if the user does not want a relative comparison, they can always set the epsilon to zero. In fact, I think that using both tolerances and taking the most forgiving one is the superior option to implement things like the Approx Matcher (#1499).