Allow for `Catch::Approx` to be used in a `constexpr` context

This commit is contained in:
Chris Thrasher 2024-05-16 13:23:38 -06:00
parent 4e8d92bf02
commit c974e30974
No known key found for this signature in database
GPG Key ID: 56FB686C9DFC8E2C
3 changed files with 72 additions and 87 deletions

View File

@ -56,7 +56,6 @@ function(add_warnings_to_targets targets)
"-Wexit-time-destructors" "-Wexit-time-destructors"
"-Wextra" "-Wextra"
"-Wextra-semi" "-Wextra-semi"
"-Wfloat-equal"
"-Wglobal-constructors" "-Wglobal-constructors"
"-Winit-self" "-Winit-self"
"-Wmisleading-indentation" "-Wmisleading-indentation"

View File

@ -10,73 +10,14 @@
#include <catch2/internal/catch_reusable_string_stream.hpp> #include <catch2/internal/catch_reusable_string_stream.hpp>
#include <cmath> #include <cmath>
#include <limits>
namespace {
// Performs equivalent check of std::fabs(lhs - rhs) <= margin
// But without the subtraction to allow for INFINITY in comparison
bool marginComparison(double lhs, double rhs, double margin) {
return (lhs + margin >= rhs) && (rhs + margin >= lhs);
}
}
namespace Catch { namespace Catch {
Approx::Approx ( double value ) std::string Approx::toString() const {
: m_epsilon( static_cast<double>(std::numeric_limits<float>::epsilon())*100. ), ReusableStringStream rss;
m_margin( 0.0 ), rss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )";
m_scale( 0.0 ), return rss.str();
m_value( value ) }
{}
Approx Approx::custom() {
return Approx( 0 );
}
Approx Approx::operator-() const {
auto temp(*this);
temp.m_value = -temp.m_value;
return temp;
}
std::string Approx::toString() const {
ReusableStringStream rss;
rss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )";
return rss.str();
}
bool Approx::equalityComparisonImpl(const double other) const {
// First try with fixed margin, then compute margin based on epsilon, scale and Approx's value
// Thanks to Richard Harris for his help refining the scaled margin value
return marginComparison(m_value, other, m_margin)
|| marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(std::isinf(m_value)? 0 : m_value)));
}
void Approx::setMargin(double newMargin) {
CATCH_ENFORCE(newMargin >= 0,
"Invalid Approx::margin: " << newMargin << '.'
<< " Approx::Margin has to be non-negative.");
m_margin = newMargin;
}
void Approx::setEpsilon(double newEpsilon) {
CATCH_ENFORCE(newEpsilon >= 0 && newEpsilon <= 1.0,
"Invalid Approx::epsilon: " << newEpsilon << '.'
<< " Approx::epsilon has to be in [0, 1]");
m_epsilon = newEpsilon;
}
namespace literals {
Approx operator ""_a(long double val) {
return Approx(val);
}
Approx operator ""_a(unsigned long long val) {
return Approx(val);
}
} // end namespace literals
std::string StringMaker<Catch::Approx>::convert(Catch::Approx const& value) { std::string StringMaker<Catch::Approx>::convert(Catch::Approx const& value) {
return value.toString(); return value.toString();

View File

@ -11,26 +11,67 @@
#include <catch2/catch_tostring.hpp> #include <catch2/catch_tostring.hpp>
#include <type_traits> #include <type_traits>
#include <limits>
namespace Catch { namespace Catch {
class Approx { class Approx {
private: private:
bool equalityComparisonImpl(double other) const; // Performs equivalent check of std::fabs(lhs - rhs) <= margin
// Sets and validates the new margin (margin >= 0) // But without the subtraction to allow for INFINITY in comparison
void setMargin(double margin); constexpr bool marginComparison (double lhs, double rhs, double margin) const {
return (lhs + margin >= rhs) && (rhs + margin >= lhs);
}
constexpr double fabs(double value) const {
return (value < 0.0) ? -value : value;
}
constexpr bool isinf(double value) const {
return value == std::numeric_limits<double>::infinity() || value == -std::numeric_limits<double>::infinity();
}
constexpr bool equalityComparisonImpl(double other) const {
// First try with fixed margin, then compute margin based on epsilon, scale and Approx's value
// Thanks to Richard Harris for his help refining the scaled margin value
return marginComparison(m_value, other, m_margin)
|| marginComparison(m_value, other, m_epsilon * (m_scale + fabs(isinf(m_value)? 0 : m_value)));
}
// Sets and validates the new epsilon (0 < epsilon < 1) // Sets and validates the new epsilon (0 < epsilon < 1)
void setEpsilon(double epsilon); constexpr void setEpsilon(double epsilon) {
if(epsilon < 0)
throw std::domain_error("Invalid Approx::epsilon. Approx::epsilon has to be in [0, 1]");
m_epsilon = epsilon;
}
// Sets and validates the new margin (margin >= 0)
constexpr void setMargin(double margin) {
if(margin < 0)
throw std::domain_error("Invalid Approx::margin. Approx::Margin has to be non-negative.");
m_margin = margin;
}
public: public:
explicit Approx ( double value ); constexpr inline explicit Approx ( double value )
: m_epsilon( static_cast<double>(std::numeric_limits<float>::epsilon())*100. ),
m_margin( 0.0 ),
m_scale( 0.0 ),
m_value( value )
{}
static Approx custom(); static constexpr Approx custom() {
return Approx( 0.0 );
}
Approx operator-() const; constexpr Approx operator-() const {
auto temp(*this);
temp.m_value = -temp.m_value;
return temp;
}
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
Approx operator()( T const& value ) const { constexpr Approx operator()( T const& value ) const {
Approx approx( static_cast<double>(value) ); Approx approx( static_cast<double>(value) );
approx.m_epsilon = m_epsilon; approx.m_epsilon = m_epsilon;
approx.m_margin = m_margin; approx.m_margin = m_margin;
@ -39,67 +80,67 @@ namespace Catch {
} }
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
explicit Approx( T const& value ): Approx(static_cast<double>(value)) constexpr inline explicit Approx( T const& value ): Approx(static_cast<double>(value))
{} {}
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
friend bool operator == ( const T& lhs, Approx const& rhs ) { friend constexpr bool operator == ( const T& lhs, Approx const& rhs ) {
auto lhs_v = static_cast<double>(lhs); auto lhs_v = static_cast<double>(lhs);
return rhs.equalityComparisonImpl(lhs_v); return rhs.equalityComparisonImpl(lhs_v);
} }
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
friend bool operator == ( Approx const& lhs, const T& rhs ) { friend constexpr bool operator == ( Approx const& lhs, const T& rhs ) {
return operator==( rhs, lhs ); return operator==( rhs, lhs );
} }
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
friend bool operator != ( T const& lhs, Approx const& rhs ) { friend constexpr bool operator != ( T const& lhs, Approx const& rhs ) {
return !operator==( lhs, rhs ); return !operator==( lhs, rhs );
} }
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
friend bool operator != ( Approx const& lhs, T const& rhs ) { friend constexpr bool operator != ( Approx const& lhs, T const& rhs ) {
return !operator==( rhs, lhs ); return !operator==( rhs, lhs );
} }
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
friend bool operator <= ( T const& lhs, Approx const& rhs ) { friend constexpr bool operator <= ( T const& lhs, Approx const& rhs ) {
return static_cast<double>(lhs) < rhs.m_value || lhs == rhs; return static_cast<double>(lhs) < rhs.m_value || lhs == rhs;
} }
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
friend bool operator <= ( Approx const& lhs, T const& rhs ) { friend constexpr bool operator <= ( Approx const& lhs, T const& rhs ) {
return lhs.m_value < static_cast<double>(rhs) || lhs == rhs; return lhs.m_value < static_cast<double>(rhs) || lhs == rhs;
} }
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
friend bool operator >= ( T const& lhs, Approx const& rhs ) { friend constexpr bool operator >= ( T const& lhs, Approx const& rhs ) {
return static_cast<double>(lhs) > rhs.m_value || lhs == rhs; return static_cast<double>(lhs) > rhs.m_value || lhs == rhs;
} }
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
friend bool operator >= ( Approx const& lhs, T const& rhs ) { friend constexpr bool operator >= ( Approx const& lhs, T const& rhs ) {
return lhs.m_value > static_cast<double>(rhs) || lhs == rhs; return lhs.m_value > static_cast<double>(rhs) || lhs == rhs;
} }
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
Approx& epsilon( T const& newEpsilon ) { constexpr Approx& epsilon( T const& newEpsilon ) {
const auto epsilonAsDouble = static_cast<double>(newEpsilon); const auto epsilonAsDouble = static_cast<double>(newEpsilon);
setEpsilon(epsilonAsDouble); setEpsilon(epsilonAsDouble);
return *this; return *this;
} }
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
Approx& margin( T const& newMargin ) { constexpr Approx& margin( T const& newMargin ) {
const auto marginAsDouble = static_cast<double>(newMargin); const auto marginAsDouble = static_cast<double>(newMargin);
setMargin(marginAsDouble); setMargin(marginAsDouble);
return *this; return *this;
} }
template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>
Approx& scale( T const& newScale ) { constexpr Approx& scale( T const& newScale ) {
m_scale = static_cast<double>(newScale); m_scale = static_cast<double>(newScale);
return *this; return *this;
} }
@ -114,8 +155,12 @@ namespace Catch {
}; };
namespace literals { namespace literals {
Approx operator ""_a(long double val); constexpr Approx operator ""_a(long double val) {
Approx operator ""_a(unsigned long long val); return Approx(val);
}
constexpr Approx operator ""_a(unsigned long long val) {
return Approx(val);
}
} // end namespace literals } // end namespace literals
template<> template<>