Catch2 icon indicating copy to clipboard operation
Catch2 copied to clipboard

Add floating-point matcher similar to numpy's and pytorch's isclose

Open danra opened this issue 2 years ago • 2 comments

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, Approx only uses relative margin comparison. This means that Approx(0) == X only passes for X == 0: This is avoided with the default values.
  • Approx is not a matcher

danra avatar Dec 01 '23 20:12 danra

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

chaitu9780 avatar Jul 31 '24 04:07 chaitu9780

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 // For std::abs #include // For std::ostringstream #include // For std::cout and std::endl

// Define the matcher to check if numbers are close class IsClose : public Catch::Matchers::MatcherBase { double m_target; // The number we want to be close to double m_rtol; // The relative tolerance double m_atol; // The absolute tolerance

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 image

Please check and give us the suggestions

chaitu9780 avatar Aug 01 '24 17:08 chaitu9780