mirror of
				https://github.com/catchorg/Catch2.git
				synced 2025-10-31 12:17:11 +01:00 
			
		
		
		
	Allow for Catch::Approx to be used in a constexpr context
				
					
				
			This commit is contained in:
		| @@ -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" | ||||||
|   | |||||||
| @@ -10,74 +10,15 @@ | |||||||
| #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 ) |  | ||||||
|     :   m_epsilon( static_cast<double>(std::numeric_limits<float>::epsilon())*100. ), |  | ||||||
|         m_margin( 0.0 ), |  | ||||||
|         m_scale( 0.0 ), |  | ||||||
|         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 { | std::string Approx::toString() const { | ||||||
|     ReusableStringStream rss; |     ReusableStringStream rss; | ||||||
|     rss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )"; |     rss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )"; | ||||||
|     return rss.str(); |     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(); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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<> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Chris Thrasher
					Chris Thrasher