Catch2: 使用相对误差检查结果是否为0.0失败

huangapple go评论68阅读模式
英文:

Catch2: Checking if result is 0.0 with a relative error fails

问题

I'm using Catch2 to check the result of a floating point calculation.
It fails with the message:

Failure:
  REQUIRE_THAT(quat.R_component_1(), Catch::WithinRel(0.0f, ALLOWED_RELATIVE_ERROR))
with expansion:
  0.0 and 0 are within 0.01% of each other

It seems that Catch2 is comparing a floating point value with an integer value, but if have no idea where the integer value is coming from. Or maybe the failure message is not accurate and I'm doing something else wrong?

This is the unit test which produces the failure:

constexpr double ALLOWED_RELATIVE_ERROR = 0.0001; // 0.01%

TEST_CASE("Euler angles [-135.0, 45.0, -90.0] are converted correctly")
{
    constexpr double PHI_DEG = -135.0; // rotation around x-axis
    constexpr double THETA_DEG = 45.0; // rotation around y-axis
    constexpr double PSI_DEG = -90.0; // rotation around z-axis

    auto quat = eulerYZXToQuaternion(PHI_DEG, THETA_DEG, PSI_DEG);
    REQUIRE_THAT(quat.R_component_1(), Catch::WithinRel(0.0, ALLOWED_RELATIVE_ERROR)); // This line fails with the message posted above.
    REQUIRE_THAT(quat.R_component_2(), Catch::WithinRel(-0.7071068, ALLOWED_RELATIVE_ERROR));
    REQUIRE_THAT(quat.R_component_3(), Catch::WithinRel(0.7071068, ALLOWED_RELATIVE_ERROR));
    REQUIRE_THAT(quat.R_component_4(), Catch::WithinRel(0.0, ALLOWED_RELATIVE_ERROR));
}

quat is of type boost::math::quaternion<double>

These are the functions to be tested:

double degToRad(double degrees) { return degrees * M_PI / 180.0; }

boost::math::quaternion<double> eulerYZXToQuaternion(double phi, double theta, double psi)
{
    // Convert angles from degrees to radians
    phi = degToRad(phi); // rotation around x-axis
    theta = degToRad(theta); // rotation around y-axis
    psi = degToRad(psi); // rotation around z-axis

    double half_phi = 0.5 * phi;
    double half_theta = 0.5 * theta;
    double half_psi = 0.5 * psi;

    double cos_half_phi = cos(half_phi);
    double sin_half_phi = sin(half_phi);
    double cos_half_theta = cos(half_theta);
    double sin_half_theta = sin(half_theta);
    double cos_half_psi = cos(half_psi);
    double sin_half_psi = sin(half_psi);

    boost::math::quaternion<double> q(1, 0, 0, 0);

    // Compute the quaternion components. Rotation in sequence Y, Z, X
    q *= boost::math::quaternion<double>(cos_half_theta, 0.0, sin_half_theta, 0.0); // y
    q *= boost::math::quaternion<double>(cos_half_psi, 0.0, 0.0, sin_half_psi); // z
    q *= boost::math::quaternion<double>(cos_half_phi, sin_half_phi, 0.0, 0.0); // x
    return q;
}

When I'm doing the check in the following way it works as expected:
REQUIRE(quat.R_component_1() == Approx(0.0).margin(ALLOWED_RELATIVE_ERROR));

Edit:
When I'm using Catch::WithinAbs() instead of Catch::WithinRel() the test passes.
But why does the check on quat.R_component_4() pass with the relative error check?

constexpr double ALLOWED_RELATIVE_ERROR = 0.0001; // 0.01%
constexpr double ALLOWED_ABSOLUTE_ERROR = 1e-9;

TEST_CASE("Euler angles [-135.0, 45.0, -90.0] are converted correctly")
{
    constexpr double PHI_DEG = -135.0; // rotation around x-axis
    constexpr double THETA_DEG = 45.0; // rotation around y-axis
    constexpr double PSI_DEG = -90.0; // rotation around z-axis

    auto quat = eulerYZXToQuaternion(PHI_DEG, THETA_DEG, PSI_DEG);
    REQUIRE_THAT(quat.R_component_1(), Catch::WithinAbs(0.0, ALLOWED_ABSOLUTE_ERROR)); // Changed to Catch::WithinAbs()
    REQUIRE_THAT(quat.R_component_2(), Catch::WithinRel(-0.7071068, ALLOWED_RELATIVE_ERROR));
    REQUIRE_THAT(quat.R_component_3(), Catch::WithinRel(0.7071068, ALLOWED_RELATIVE_ERROR));
    REQUIRE_THAT(quat.R_component_4(), Catch::WithinRel(0.0, ALLOWED_RELATIVE_ERROR));
}
英文:

I'm using Catch2 to check the result of a floating point calculation.
It fails with the message:

Failure:
  REQUIRE_THAT(quat.R_component_1(), Catch::WithinRel(0.0f, ALLOWED_RELATIVE_ERROR))
with expansion:
  0.0 and 0 are within 0.01% of each other

It seems that Catch2 is comparing a floating point value with an integer value, but if have no idea where the integer value is coming from. Or maybe the failure message is not accurate and I'm doing somethin else wrong?

This is the unit test which produces the failure:

constexpr double ALLOWED_RELATIVE_ERROR = 0.0001; // 0.01%

TEST_CASE(&quot;Euler angles [-135.0, 45.0, -90.0] are converted correctly&quot;)
    {
        constexpr double PHI_DEG = -135.0; // rotation around x-axis
        constexpr double THETA_DEG = 45.0; // rotation around y-axis
        constexpr double PSI_DEG = -90.0; // rotation around z-axis

        auto quat = eulerYZXToQuaternion(PHI_DEG, THETA_DEG, PSI_DEG);
        REQUIRE_THAT(quat.R_component_1(), Catch::WithinRel(0.0, ALLOWED_RELATIVE_ERROR)); // This line fails with the message posted above.
        REQUIRE_THAT(quat.R_component_2(), Catch::WithinRel(-0.7071068, ALLOWED_RELATIVE_ERROR));
        REQUIRE_THAT(quat.R_component_3(), Catch::WithinRel(0.7071068, ALLOWED_RELATIVE_ERROR));
        REQUIRE_THAT(quat.R_component_4(), Catch::WithinRel(0.0, ALLOWED_RELATIVE_ERROR));
    }

quat is of type boost::math::quaternion&lt;double&gt;

These are the functions to be tested:

double degToRad(double degrees) { return degrees * M_PI / 180.0; }

boost::math::quaternion&lt;double&gt; eulerYZXToQuaternion(double phi, double theta, double psi)
{
    // Convert angles from degrees to radians
    phi = degToRad(phi); // rotation around x-axis
    theta = degToRad(theta); // rotation around y-axis
    psi = degToRad(psi); // rotation around z-axis

    double half_phi = 0.5 * phi;
    double half_theta = 0.5 * theta;
    double half_psi = 0.5 * psi;

    double cos_half_phi = cos(half_phi);
    double sin_half_phi = sin(half_phi);
    double cos_half_theta = cos(half_theta);
    double sin_half_theta = sin(half_theta);
    double cos_half_psi = cos(half_psi);
    double sin_half_psi = sin(half_psi);

    boost::math::quaternion&lt;double&gt; q(1, 0, 0, 0);

    // Compute the quaternion components. Rotation in sequence Y, Z, X
    q *= boost::math::quaternion&lt;double&gt;(cos_half_theta, 0.0, sin_half_theta, 0.0); // y
    q *= boost::math::quaternion&lt;double&gt;(cos_half_psi, 0.0, 0.0, sin_half_psi); // z
    q *= boost::math::quaternion&lt;double&gt;(cos_half_phi, sin_half_phi, 0.0, 0.0); // x
    return q;
}

When I'm doing the check in the following way it works as expected:
REQUIRE(quat.R_component_1() == Approx(0.0).margin(ALLOWED_RELATIVE_ERROR));

Edit:
When I'm using Catch::WithinAbs() instead of Catch::WithinRel() the test passes.
But why does the check on quat.R_component_4() pass with the relative error check?

constexpr double ALLOWED_RELATIVE_ERROR = 0.0001; // 0.01%
constexpr double ALLOWED_ABSOLUT_ERROR = 1e-9;

TEST_CASE(&quot;Euler angles [-135.0, 45.0, -90.0] are converted correctly&quot;)
    {
        constexpr double PHI_DEG = -135.0; // rotation around x-axis
        constexpr double THETA_DEG = 45.0; // rotation around y-axis
        constexpr double PSI_DEG = -90.0; // rotation around z-axis

        auto quat = eulerYZXToQuaternion(PHI_DEG, THETA_DEG, PSI_DEG);
        REQUIRE_THAT(quat.R_component_1(), Catch::WithinAbs(0.0, ALLOWED_ABSOLUT_ERROR)); // Changed to Catch::WithinAbs()
        REQUIRE_THAT(quat.R_component_2(), Catch::WithinRel(-0.7071068, ALLOWED_RELATIVE_ERROR));
        REQUIRE_THAT(quat.R_component_3(), Catch::WithinRel(0.7071068, ALLOWED_RELATIVE_ERROR));
        REQUIRE_THAT(quat.R_component_4(), Catch::WithinRel(0.0, ALLOWED_RELATIVE_ERROR));
    }

答案1

得分: 3

答案可以在这里找到:https://github.com/catchorg/Catch2/blob/devel/docs/comparing-floating-point-numbers.md

具体在"WithinRel"部分,他们告诉你它匹配的条件是:

|arg - target| <= eps * max(|arg|, |target|)

请注意,如果target为0,这将简化为:|arg| <= eps * |arg|,只有在eps < 1的情况下才为真,前提是arg == 0。实际上,通过在目标中使用0来使用WithinRel,您禁用了相对部分。

至于您的失败消息。如果我这样做:

double bla = 0.00000000001;
REQUIRE_THAT(bla, Catch::Matchers::WithinRel(0.0, 0.0001));

Catch2会打印:

FAILED:
  REQUIRE_THAT( bla, Catch::Matchers::WithinRel(0.0, 0.0001) )
with expansion:
  0.0 and 0 are within 0.01% of each other

这是因为双精度浮点数通常不会以完全的小数精度打印,所以在这种情况下,0.00000000001打印为0.0

所以总结一下:

quat.R_component_1()小于1e-9但实际上不等于0,所以WithinRel失败,而WithinAbs成功。

quat.R_component_4()实际上等于0,所以WithinRel成功。

英文:

The answer can be found here: https://github.com/catchorg/Catch2/blob/devel/docs/comparing-floating-point-numbers.md

Specifically in the WithinRel section, where they tell you that it matches:

|arg - target| &lt;= eps * max(|arg|, |target|)

Note that if target is 0 this reduces to: |arg| &lt;= eps * |arg| which is only true if arg == 0 (as long as eps &lt; 1). Essentially by using WithinRel with a target of 0 you disable the relative part.

As for your failure message. If I do this:

double bla = 0.00000000001;
REQUIRE_THAT(bla, Catch::Matchers::WithinRel(0.0, 0.0001));

Catch2 prints:

FAILED:
  REQUIRE_THAT( bla, Catch::Matchers::WithinRel(0.0, 0.0001) )
with expansion:
  0.0 and 0 are within 0.01% of each other

Which is because doubles typically are not printed with full decimal precision, so in this case 0.00000000001 prints as 0.0.

So to sum up:

quat.R_component_1() is less than 1e-9 but not actually 0, so WithinRel fails while WithinAbs succeeds.

quat.R_component_4() is actually equal to 0 so WithinRel succeeds.

huangapple
  • 本文由 发表于 2023年7月3日 15:07:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/76602549.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定