Approx for containers

48 views
Skip to first unread message

Joes Staal

unread,
Jul 7, 2016, 10:17:15 AM7/7/16
to CATCH
Hi,

I've asked about this before, and got something working for my self. What I did was defining my own approx on top of Approx:


#include "catch.hpp"

namespace Catch {

    template <typename T> bool approximately_equal(const T& lhs, const T& rhs, double epsilon, double scale);

    template <typename T> bool approximately_equal(std::vector<T> const& lhs, std::vector<T> const& rhs, double epsilon, double scale)
    {
        if (lhs.size() != rhs.size())
            return false;

        bool are_equal = true;
        for (auto i = 0u; i < lhs.size(); ++i)
        {
            are_equal = are_equal && approximately_equal(rhs[i], lhs[i], epsilon, scale);
            if (!are_equal)
                break;
        }
        return are_equal;
    }

    template <> inline bool approximately_equal(double const& lhs, double const& rhs, double epsilon, double scale)
    {
        // Thanks to Richard Harris for his help refining this formula
        return fabs(lhs - rhs) < epsilon * (scale + (std::max)(fabs(lhs), fabs(rhs)));
    }

    template <typename T> 
    class Approx {
    public:
        explicit Approx(const T& value)
        : m_epsilon(std::numeric_limits<float>::epsilon() * 100)
        , m_scale(1.0)
        , m_value(value)
        {}

        Approx(Approx const& other)
        : m_epsilon(other.m_epsilon)
        , m_scale(other.m_scale)
        , m_value(other.m_value)
        {}

        static Approx custom() {
            return Approx(T());
        }

        Approx operator()(const T& value) {
            Approx approx(value);
            approx.epsilon(m_epsilon);
            approx.scale(m_scale);
            return approx;
        }

        friend bool operator == (const T& lhs, Approx const& rhs) {
            return approximately_equal(lhs, rhs.m_value, rhs.m_epsilon, rhs.m_scale);
        }

        friend bool operator == (Approx const& lhs, const T& rhs) {
            return operator==(rhs, lhs);
        }

        friend bool operator != (const T& lhs, Approx const& rhs) {
            return !operator==(lhs, rhs);
        }

        friend bool operator != (Approx const& lhs, const T& rhs) {
            return !operator==(rhs, lhs);
        }

        Approx& epsilon(double newEpsilon) {
            m_epsilon = newEpsilon;
            return *this;
        }

        Approx& scale(double newScale) {
            m_scale = newScale;
            return *this;
        }

        std::string toString() const {
            std::ostringstream oss;
            oss << "Approx( " << Catch::toString(m_value) << " )";
            return oss.str();
        }

    private:
        double m_epsilon;
        double m_scale;
        T m_value;
    };
}  // end namespace Catch

namespace Catch
    {

    template<>
    inline std::string toString<Catch::Approx<std::vector<double> > >(Catch::Approx<std::vector<double> > const& value) {
        return value.toString();
    }

    template<>
    inline std::string toString<Catch::Approx<double> >(Catch::Approx<double> const& value) {
        return value.toString();
    }
}

namespace
{
    template <typename T> Catch::Approx<T> approx(T const& t)
    {
        return Catch::Approx<T>(t);
    }

    template <typename T> Catch::Approx<T> approx(T const& t, double epsilon)
    {
        auto a = Catch::Approx<T>(t);
        a.epsilon(epsilon);
        return a;
    }
}

This allows me to write:

std::vector<double> v1 = { 1, 2, 3 };
std::vector<double> v2 = { 1, 2.1, 3 };

REQUIRE(v1 == approx(v2, 1.0e-3));

This is nice, but what I would really like is if I could highlight in the expansion the locations where the two vectors start to differ. Anyone an idea how I could accomplish that?

Cheers,

Joes

Phil Nash

unread,
Jul 21, 2016, 2:33:56 AM7/21/16
to CATCH
Sorry for the delay getting back to you again, Joes.
I'm forgetting what I said before now (if anything), but looking at this again fresh my recommendation would be to write a Matcher for it. That would give you syntax like:

REQUIRE_THAT( v1, ContainsApproximately( v2 ).withEpsilon( 1.0e-3 ) );

In a matcher you supply a match() method, which will do the comparison (in this case the equivalent of your approximately_equal function) and a toString() method that allows your to provide a description of what went wrong (you could use this to somehow highlight the position where they diverge).
Have a look at the existing Matchers (e.g. StartsWith) for more details (not yet documented, I'm afraid).

Joes Staal

unread,
Jul 26, 2016, 11:05:53 AM7/26/16
to CATCH
Dear Phil,

Thank you for your suggestion. I've been playing around a bit and got to the following first implementation:

struct ContainsApproximately : Catch::Matchers::Impl::MatcherImpl<ContainsApproximately, std::vector<float> > 
{
    ContainsApproximately(std::vector<float> const& v)
        : m_data(v) 
    {}

    ContainsApproximately(ContainsApproximately const& other)
        : m_data(other.m_data) 
        , m_scale(other.m_scale)
        , m_epsilon(other.m_epsilon)
        , m_index_first_unequal_value(other.m_index_first_unequal_value)
        , m_first_unequal_value(other.m_first_unequal_value)
    {}

    virtual ~ContainsApproximately() = default;

    virtual bool match(std::vector<float> const& expr) const override
    {
        for (auto i = 0u; i < m_data.size(); ++i)
        {
            if (!approximately_equal(expr[i], m_data[i]))
            {
                m_first_unequal_value = expr[i];
                m_index_first_unequal_value = i;
                return false;
            }
        }
        return true; 
    }

    virtual std::string toString() const override
    {
        return "Values are not approximately equal at index " + std::to_string(m_index_first_unequal_value) + ", expected " +            
               std::to_string(m_data[m_index_first_unequal_value]) + ", found " + std::to_string(m_first_unequal_value) + ".";
    }

private:

    bool approximately_equal(double const& lhs, double const& rhs) const
    {
        // Thanks to Richard Harris for his help refining this formula
        return fabs(lhs - rhs) < m_epsilon * (m_scale + (std::max)(fabs(lhs), fabs(rhs)));
    }

    std::vector<float> m_data;
    double m_scale = 1.0;
    const double m_epsilon = 1e-3;
    mutable std::size_t m_index_first_unequal_value = 0;
    mutable float m_first_unequal_value = std::numeric_limits<float>::quiet_NaN();
};

I've written a first simple test:
TEST_CASE("Vectors differ at index 1")
{
    std::vector<float> v1{ 1.0f, 2.0f, 3.0f }, v2{ 1.0f, 2.1f, 3.0f };
    REQUIRE_THAT(v1, ContainsApproximately(v2));
}

This test fails, as it should, however the output incorrect:

FAILED:
  REQUIRE_THAT( v1, ContainsApproximately(v2) )
with expansion:
  { 1.0f, 2.0f, 3.0f } Values are not approximately equal at index 0, expected
  1.000000, found nan.

Am I missing something? A copy constructor/assignment operatior? Or is having state that is being changed in the match function not possible?

Cheers,

Joes

Joes Staal

unread,
Aug 2, 2016, 8:36:02 AM8/2/16
to CATCH
Enter code here...

Hi,

I've delved into the internals of REQUIRE_THAT, which depends on INTERNAL_CHECK_THAT. It turns out that one of the first things that happens in INTERNAL_CHECK_THAT is a call to the matcher's toString() function before the match function has been called. When toString() is called, the internal state hasn't been set, which explains why the failure message is incorrect.
By changing the order of execution, I can get the behaviour I want, as long as I use a named variable for ContainsApproximately. With the following definition for INTERNAL_CHECK_THAT

#define INTERNAL_CHECK_THAT( arg, matcher, resultDisposition, macroName ) \
    do { \
        Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg ", " #matcher, resultDisposition ); \
        try { \
            __catchResult \
                .setLhs( Catch::toString( arg ) ) \
                .setOp( "matches" ) \
                .setResultType( (matcher).match( arg ) ); \
            std::string matcherAsString = (matcher).toString(); \
            __catchResult.setRhs( matcherAsString == Catch::Detail::unprintableString ? #matcher : matcherAsString ); \
            __catchResult.captureExpression(); \
        } catch( ... ) { \
            __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \
        } \
        INTERNAL_CATCH_REACT( __catchResult ) \
    } while( Catch::alwaysFalse() )


I can do:

TEST_CASE("Vectors differ at index 1")
{
    std::vector<float> v1{ 1.0f, 2.0f, 3.0f }, v2{ 1.0f, 2.1f, 3.0f };
    auto matcher = ContainsApproximately(v2);
    REQUIRE_THAT(v1, matcher);
}

and get as output:

FAILED:                                                     
  REQUIRE_THAT( v1, matcher )                                      
with expansion:                                                                      
  { 1.0f, 2.0f, 3.0f } Values are not approximately equal at index 1, expected       
  2.100000, found 2.000000.                                                          

If I use an unnamed variable, the output still uses index 0 and nan. That can be solved by making a copy of the 
matcher inside the macro:

#define INTERNAL_CHECK_THAT( arg, matcher, resultDisposition, macroName ) \
    do { \
        Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg ", " #matcher, resultDisposition ); \
	auto matcher_copy(matcher.clone()); \
        try { \
            __catchResult \
                .setLhs( Catch::toString( arg ) ) \
                .setOp( "matches" ) \
                .setResultType( (matcher_copy)->match( arg ) ); \
            std::string matcherAsString = (matcher_copy)->toString(); \
            __catchResult.setRhs( matcherAsString == Catch::Detail::unprintableString ? #matcher : matcherAsString ); \
            __catchResult.captureExpression(); \
        } catch( ... ) { \
            __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \
        } \
        INTERNAL_CATCH_REACT( __catchResult ) \
    } while( Catch::alwaysFalse() )

There is a problem with this solution though: the use of 
auto. This won't work on C++03. That can probably be solved by storing a typedef to the type of the matcher its base class, but I haven't looked into it. I would first like to know if there is a better way of doing this.

Cheers,

Joes
Reply all
Reply to author
Forward
0 new messages