diff --git a/include/gul14/num_util.h b/include/gul14/num_util.h index 63ddadf3..ba6c78ae 100644 --- a/include/gul14/num_util.h +++ b/include/gul14/num_util.h @@ -4,7 +4,7 @@ * \authors \ref contributors * \date Created on 7 Feb 2019 * - * \copyright Copyright 2019-2023 Deutsches Elektronen-Synchrotron (DESY), Hamburg + * \copyright Copyright 2019-2024 Deutsches Elektronen-Synchrotron (DESY), Hamburg * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published @@ -29,9 +29,48 @@ #include #include "gul14/internal.h" +#include "gul14/traits.h" namespace gul14 { +/// \cond HIDE_SYMBOLS +namespace detail{ + +template +class CanCallStdAbsOn + : public std::false_type {}; + +template +class CanCallStdAbsOn()))>> + : public std::true_type {}; + +// abs() for unsigned integers +template +constexpr auto abs_impl(ValueT n) noexcept -> std::enable_if_t< + std::is_unsigned::value, ValueT> +{ + return n; +} + +// abs() for types supported by std::abs() +template +constexpr auto abs_impl(ValueT n) noexcept -> std::enable_if_t< + not std::is_unsigned::value and CanCallStdAbsOn::value, ValueT> +{ + return std::abs(n); +} + +// Fallback: Try to find a user-supplied abs() via ADL +template +constexpr auto abs_impl(ValueT n) noexcept -> std::enable_if_t< + not std::is_unsigned::value and not CanCallStdAbsOn::value, ValueT> +{ + return abs(n); +} + +} // namespace detail +/// \endcond + /** * \addtogroup num_util_h gul14/num_util.h * \brief Numerical utility functions. @@ -41,27 +80,20 @@ namespace gul14 { /** * Compute the absolute value of a number. * - * This function is almost equal to std::abs() with the exception of unsigned integral - * types, which are returned unchanged and in their original type. This is especially - * useful in templates, where std::abs() cannot be used for all arithmetic types. + * This function makes three attempts at compile time to determine the absolute value of + * the given number. First, if the value is of an unsigned integer type, it is returned + * unmodified. Second, if std::abs() can be called on the value, its result is returned. + * In the third and last attempt, abs() is called on the value in the hope that a suitable + * user-supplied function can be found via argument-dependent lookup. * * \param n The number whose absolute value should be determined. * * \returns the absolute value of n. */ template -constexpr auto abs(ValueT n) noexcept -> std::enable_if_t::value, ValueT> -{ - return n; -} - -/** - * \overload - */ -template -constexpr auto abs(ValueT n) noexcept -> std::enable_if_t::value, ValueT> +constexpr auto abs(ValueT n) noexcept { - return std::abs(n); + return detail::abs_impl(n); } /** @@ -114,6 +146,10 @@ bool within_orders(const NumT a, const NumT b, const OrderT orders) noexcept(fal * \param tol The absolute tolerance * * \returns true if the absolute difference between a and b is smaller than tol. + * + * \note + * This function supports user-defined numeric types as long as they provide an + * implementation of abs() that can be found via argument-dependent lookup. */ template bool within_abs(NumT a, NumT b, NumT tol) noexcept { diff --git a/tests/test_num_util.cc b/tests/test_num_util.cc index 288736a1..99c5ffc7 100644 --- a/tests/test_num_util.cc +++ b/tests/test_num_util.cc @@ -4,7 +4,7 @@ * \date Created on 7 Feb 2019 * \brief Unit tests for within_orders(), within_abs(), and within_ulp(). * - * \copyright Copyright 2019 Deutsches Elektronen-Synchrotron (DESY), Hamburg + * \copyright Copyright 2019-2024 Deutsches Elektronen-Synchrotron (DESY), Hamburg * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published @@ -25,6 +25,30 @@ #include "gul14/catch.h" #include "gul14/num_util.h" +namespace { + +// Simple physical unit class for meters +class Meters { + double value_{ 0.0 }; +public: + explicit Meters(double value) : value_{ value } {} + double value() const { return value_; } + bool operator==(const Meters& other) const { return value_ == other.value_; } + bool operator!=(const Meters& other) const { return value_ != other.value_; } + bool operator<(const Meters& other) const { return value_ < other.value_; } + bool operator>(const Meters& other) const { return value_ > other.value_; } + bool operator<=(const Meters& other) const { return value_ <= other.value_; } + bool operator>=(const Meters& other) const { return value_ >= other.value_; } + Meters operator-() const { return Meters{ -value_ }; } + Meters operator+(const Meters& other) const { return Meters{ value_ + other.value_ }; } + Meters operator-(const Meters& other) const { return Meters{ value_ - other.value_ }; } +}; + +// abs() implementation, usable via ADL +Meters abs(Meters m) { return Meters(std::abs(m.value())); } + +} // anonymous namespace + TEST_CASE("test within_orders()", "[num_util]") { REQUIRE(gul14::within_orders(1.0, 101.0, 2) == false); @@ -272,6 +296,10 @@ TEST_CASE("test within_abs()", "[num_util]") auto i2 = i1 - 1; auto i3 = 60; REQUIRE(gul14::within_abs(i1, i2, i3) == true); + + // Physical units + REQUIRE(gul14::within_abs(Meters{ 1.0 }, Meters{ 1.01 }, Meters{ 0.02 })); + REQUIRE(gul14::within_abs(Meters{ 42.0 }, Meters{ 42.5 }, Meters{ 0.4 }) == false); } TEST_CASE("test within_ulp()", "[num_util]")