Approx rework: default scale == 0, epsilon applies to Approx::value

Also adds check to Approx::epsilon that the new epsilon has a valid
(ie one between 0 and 1)

Based on
http://realtimecollisiondetection.net/blog/?p=89
https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
https://en.wikipedia.org/wiki/Approximation_error#Formal_Definition

The given epsilon should refer to the target value, otherwise
the result would be unexpected, e.g. 101.02 == Approx(100).epsilon(0.01)
is true.
The default scale should be invisible, thus,
e.g. 101.01 == Approx(100).epsilon(0.01) gets false.
Finally even 101.000001 == Approx(100).epsilon(0.01) is false
This commit is contained in:
Pfiffikus 2017-10-26 09:19:57 +02:00 committed by Martin Hořeňovský
parent ae21020640
commit 00af677577
2 changed files with 9 additions and 4 deletions

View File

@ -23,7 +23,7 @@ namespace Detail {
Approx::Approx ( double value ) Approx::Approx ( double value )
: m_epsilon( std::numeric_limits<float>::epsilon()*100 ), : m_epsilon( std::numeric_limits<float>::epsilon()*100 ),
m_margin( 0.0 ), m_margin( 0.0 ),
m_scale( 1.0 ), m_scale( 0.0 ),
m_value( value ) m_value( value )
{} {}

View File

@ -44,8 +44,9 @@ namespace Detail {
friend bool operator == ( const T& lhs, Approx const& rhs ) { friend bool operator == ( const T& lhs, Approx const& rhs ) {
// Thanks to Richard Harris for his help refining this formula // Thanks to Richard Harris for his help refining this formula
auto lhs_v = static_cast<double>(lhs); auto lhs_v = static_cast<double>(lhs);
bool relativeOK = std::fabs(lhs_v - rhs.m_value) < rhs.m_epsilon * (rhs.m_scale +
dmax(std::fabs(lhs_v), std::fabs(rhs.m_value))); bool relativeOK = std::fabs( lhs_v - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + std::fabs(rhs.m_value) );
if (relativeOK) { if (relativeOK) {
return true; return true;
} }
@ -89,7 +90,11 @@ namespace Detail {
template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
Approx& epsilon( T const& newEpsilon ) { Approx& epsilon( T const& newEpsilon ) {
m_epsilon = static_cast<double>(newEpsilon); double asDouble = static_cast<double>(newEpsilon);
CATCH_ENFORCE(asDouble >= 0 && asDouble <= 1.0,
"Invalid Approx::epsilon: " << m_epsilon <<
", Approx::epsilon has to be between 0 and 1");
m_epsilon = asDouble;
return *this; return *this;
} }