mirror of
https://github.com/catchorg/Catch2.git
synced 2024-12-25 04:23:30 +01:00
Allow for Catch::Approx
to be used in a constexpr
context
This commit is contained in:
parent
4e8d92bf02
commit
c974e30974
@ -56,7 +56,6 @@ function(add_warnings_to_targets targets)
|
||||
"-Wexit-time-destructors"
|
||||
"-Wextra"
|
||||
"-Wextra-semi"
|
||||
"-Wfloat-equal"
|
||||
"-Wglobal-constructors"
|
||||
"-Winit-self"
|
||||
"-Wmisleading-indentation"
|
||||
|
@ -10,74 +10,15 @@
|
||||
#include <catch2/internal/catch_reusable_string_stream.hpp>
|
||||
|
||||
#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 {
|
||||
|
||||
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 {
|
||||
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) {
|
||||
return value.toString();
|
||||
}
|
||||
|
@ -11,26 +11,67 @@
|
||||
#include <catch2/catch_tostring.hpp>
|
||||
|
||||
#include <type_traits>
|
||||
#include <limits>
|
||||
|
||||
namespace Catch {
|
||||
|
||||
class Approx {
|
||||
private:
|
||||
bool equalityComparisonImpl(double other) const;
|
||||
// Sets and validates the new margin (margin >= 0)
|
||||
void setMargin(double margin);
|
||||
// Performs equivalent check of std::fabs(lhs - rhs) <= margin
|
||||
// But without the subtraction to allow for INFINITY in comparison
|
||||
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)
|
||||
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:
|
||||
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>>
|
||||
Approx operator()( T const& value ) const {
|
||||
constexpr Approx operator()( T const& value ) const {
|
||||
Approx approx( static_cast<double>(value) );
|
||||
approx.m_epsilon = m_epsilon;
|
||||
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>>
|
||||
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>>
|
||||
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);
|
||||
return rhs.equalityComparisonImpl(lhs_v);
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
setEpsilon(epsilonAsDouble);
|
||||
return *this;
|
||||
}
|
||||
|
||||
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);
|
||||
setMargin(marginAsDouble);
|
||||
return *this;
|
||||
}
|
||||
|
||||
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);
|
||||
return *this;
|
||||
}
|
||||
@ -114,8 +155,12 @@ namespace Catch {
|
||||
};
|
||||
|
||||
namespace literals {
|
||||
Approx operator ""_a(long double val);
|
||||
Approx operator ""_a(unsigned long long val);
|
||||
constexpr Approx operator ""_a(long double val) {
|
||||
return Approx(val);
|
||||
}
|
||||
constexpr Approx operator ""_a(unsigned long long val) {
|
||||
return Approx(val);
|
||||
}
|
||||
} // end namespace literals
|
||||
|
||||
template<>
|
||||
|
Loading…
Reference in New Issue
Block a user