Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Should Approx be more generic? #1467

Open
Danielmelody opened this issue Dec 6, 2018 · 3 comments
Open

Should Approx be more generic? #1467

Danielmelody opened this issue Dec 6, 2018 · 3 comments

Comments

@Danielmelody
Copy link

Currently Approx only supports types that can be converted to double.
However, For fixed-point numbers, or fuzzy match strings, Approx should also work. But it requires the user to customize their specific version.
I 'd be happy if Approx provide a template parameter and default to double. It will make Approx more usable.

Would you be ok with a PR related to this?

@Danielmelody
Copy link
Author

Another problem, double is at exp level precision, while int and fix-pointer is at the linear level, which makes Approx not that "Approx", and also makes the a relative comparison take no effects for int / fixed-point types.
It raise issues like #1444

@Danielmelody
Copy link
Author

@horenmar PTAL

@cstratopoulos
Copy link

I'd like a feature like this too.

Currently I'm using a hand-rolled implementation which uses the Matcher API and the existing Approx implementation:

template<typename T>
class IsApprox : public Catch::MatcherBase<T> {
public:
    IsApprox(T t) : _val(t) {}

    template<typename U = T>
    static constexpr auto toDouble(const U& val) noexcept
        -> decltype(units::unit_cast<double>(val))
    {
        return units::unit_cast<double>(val);
    }

    template<typename U = T>
    static constexpr auto toDouble(const U& val) noexcept -> decltype(val.count())
    {
        return val.count();
    }

    bool match(const T& t) const override
    {
        return toDouble(t) == Approx(toDouble(_val))
                                  .epsilon(toDouble(_epsilon))
                                  .margin(toDouble(_margin));
    }

    template<typename U = T>
    IsApprox& epsilon(U u)
    {
        _epsilon = T{u};
        return *this;
    }

    template<typename U = T>
    IsApprox& margin(U u)
    {
        _margin = T{u};
        return *this;
    }

    std::string describe() const override
    {
        using namespace date;

        std::ostringstream oss;
        oss << "is approximately " << _val;
        return oss.str();
    }

private:
    T _val;
    T _epsilon{std::numeric_limits<float>::epsilon() * 100};
    T _margin{0.0};
};

This is skewed towards my use cases of

  • using std::chrono::duration with Rep = double, and
  • using the units library

Maybe it can be useful in highlighting some generally applicable features. For example in the same way that Approx currently uses enable_if with convertibility to double I can do stuff like

    CHECK_THAT(pound_t{ 2.2 }, IsApprox(kilogram_t{ 1 }).margin(gram_t{ 10 }));

but I can also specify margin as a raw double, as kilos, etc.

Also note that I've provided a toDouble function so that everything can be forwarded to Approx. If we had an Approx<T> this would not be necessary, but there is still possibly an issue with fabs:
https://github.com/catchorg/Catch2/blob/master/include/internal/catch_approx.cpp#L55

This may require a customization point of some sort. In my units example above, the library provides mathematical functions such as units::math::fabs so maybe ADL could be an option.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants