Add floating-point matcher similar to numpy's and pytorch's isclose
Description
Add a floating-point matcher similar to numpy's and pytorch's isclose, testing abs(a - b) <= (atol + rtol * abs(b))
with the same defaults rtol=1e-05, atol=1e-08
Additional context
isclose is useful for matching a floating-point value to a target reference floating-point value and is a de-facto standard. It would be useful to be able to make the equivalent comparisons in Catch2 out of the box.
The matcher would behave similar to Approx. However, referencing Approx's issues:
- All internal computation is done in
doubles, leading to slightly different results if the inputs were floats: In the new matcher we can just avoid this issue. Approx's relative margin comparison is not symmetric: This is not an issue, but the desired behavior for this matcher. (the existing matchers are OK when symmetrical behavior is desired).- By default,
Approxonly uses relative margin comparison. This means thatApprox(0) == Xonly passes forX == 0: This is avoided with the default values. Approxis not a matcher
Hi @danra ,
We want to work on this issue as part of our course project. Could you please assign it to me and, if possible, help us generate the issue?
Thanks
Hi @danra
We have written the below code for the macher functionality
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_templated.hpp>
#include
// Define the matcher to check if numbers are close
class IsClose : public Catch::Matchers::MatcherBase
public: IsClose(double target, double rtol = 1e-05, double atol = 1e-08) : m_target(target), m_rtol(rtol), m_atol(atol) {}
// Check if two numbers are close using the rule
bool match(const double& matchee) const override {
// Apply the formula
bool result = std::abs(matchee - m_target) <= (m_atol + m_rtol * std::abs(m_target));
std::cout << "Matching " << matchee << " against " << m_target << ": "
<< (result ? "PASS" : "FAIL") << std::endl;
return result;
}
// Describe the matcher
std::string describe() const override {
std::ostringstream ss;
ss << "is close to " << m_target << " within tolerance (atol = " << m_atol << ", rtol = " << m_rtol << ")";
return ss.str();
}
};
// Helper to create the matcher IsClose IsCloseMatcher(double target, double rtol = 1e-05, double atol = 1e-08) { return IsClose(target, rtol, atol); }
// Example test to use the matcher TEST_CASE("IsClose matcher works as expected", "[isclose]") { double value = 0.00001; REQUIRE_THAT(value, IsCloseMatcher(0.00002)); // Check if 0.00001 is close to 0.00002 REQUIRE_THAT(value, IsCloseMatcher(0.0, 1e-05, 1e-05)); // Adjusted tolerances REQUIRE_THAT(value, IsCloseMatcher(0.00002, 1e-05, 1e-04)); REQUIRE_THAT(value, !IsCloseMatcher(0.1)); // Check if 0.00001 is not close to 0.1 }
TEST_CASE("IsClose matcher checks closeness to another value", "[isclose]") { double value = 0.00001; REQUIRE_THAT(value, IsCloseMatcher(0.00002, 1e-05, 1e-04)); // Check if 0.00001 is close to 0.00002 REQUIRE_THAT(value, !IsCloseMatcher(0.1)); // Check if 0.00001 is not close to 0.1 }
The below is the output
Please check and give us the suggestions