catchorg / Catch2

A modern, C++-native, test framework for unit-tests, TDD and BDD - using C++14, C++17 and later (C++11 support is in v2.x branch, and C++03 on the Catch1.x branch)
https://discord.gg/4CWS9zD
Boost Software License 1.0
18.67k stars 3.05k forks source link

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

Open danra opened 11 months ago

danra commented 11 months ago

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:

chaitu9780 commented 3 months ago

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 commented 3 months ago

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