英文:
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("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_ABSOLUT_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_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| <= eps * max(|arg|, |target|)
Note that if target
is 0 this reduces to: |arg| <= eps * |arg|
which is only true if arg == 0
(as long as eps < 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论