You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
159 lines
5.0 KiB
159 lines
5.0 KiB
#pragma once
|
|
|
|
#include "catch.h"
|
|
#include <limits>
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
|
|
|
|
/*
|
|
|
|
We need to compare floating point values, therefore we need an approximation
|
|
function, which Catch provides for us:
|
|
REQUIRE( performComputation() == Approx( 2.1 ));
|
|
(see https://github.com/catchorg/Catch2/blob/master/docs/assertions.md)
|
|
For example (with the default epsilon):
|
|
2.61007666588 ~= 2.61007662723
|
|
This may not be good enough for us...
|
|
|
|
Three controls exist for the comparison:
|
|
- margin (absolute difference)
|
|
|a-b| <= margin
|
|
- scale - ignored for now; see below
|
|
- epsilon (relative difference)
|
|
|a-b| <= epsilon * |b|
|
|
|
|
|
|
Catch v1 vs v2
|
|
----------------
|
|
|
|
In v1, the formula for approx was:
|
|
|a-b| <= epsilon * (scale + max( |a|, |b| ))
|
|
With the default for scale being 1.
|
|
With v2, this changed to:
|
|
|a-b| <= margin || |a-b| <= epsilon * (scale + b )
|
|
(it's really slightly different, but the gist is the above)
|
|
The scale has changed to 0!
|
|
Note that it's now only relative to the "golden" number to which we're comparing!
|
|
|
|
|
|
Absolute vs relative comparisons
|
|
----------------------------------
|
|
|
|
Absolute and relative tolerances are tested as:
|
|
|a-b| <= MARGIN
|
|
and:
|
|
|a-b| <= EPSILON * max(|a|, |b|)
|
|
|
|
The absolute tolerance test fails when x and y become large, and the relative
|
|
tolerance test fails when they become small. It is therefore best to combine
|
|
the two tests together in a single test.
|
|
|
|
But this is always subject to context: generalizing here is convenient, that's all...
|
|
|
|
|
|
Approx to 0
|
|
-------------
|
|
|
|
Because the scale is 0 in v2, and the margin defaults to 0, there is essentially no
|
|
approximate comparison to 0! We must use a margin if we want to do this.
|
|
|
|
Which value to choose is a good question, though. Because most of our math is in
|
|
floats, we choose to use the float epsilon: any two numbers are deemed equal if their
|
|
difference is less than the smallest float number representable:
|
|
*/
|
|
#if ! defined( __APPROX_MARGIN )
|
|
#define __APPROX_MARGIN std::numeric_limits<float>::epsilon()
|
|
#endif
|
|
template< typename F > struct __approx_margin {};
|
|
template<> struct __approx_margin< double > { static constexpr double value() { return __APPROX_MARGIN; } };
|
|
template<> struct __approx_margin< float > { static constexpr float value() { return __APPROX_MARGIN * 4; } };
|
|
template< typename F > F approx_margin( F ) { return __approx_margin< F >::value(); }
|
|
|
|
/*
|
|
But note that for floats, this number is scaled up!
|
|
|
|
|
|
Epsilon
|
|
---------
|
|
Approx sets its epsilon to:
|
|
std::numeric_limits<float>::epsilon()*100
|
|
This might be too big.
|
|
|
|
Instead, we set the epsilon to the same as the margin, by default:
|
|
*/
|
|
#if ! defined( __APPROX_EPSILON )
|
|
#define __APPROX_EPSILON __APPROX_MARGIN
|
|
#endif
|
|
template< typename F > struct __approx_epsilon {};
|
|
template<> struct __approx_epsilon< double > { static constexpr double value() { return __APPROX_EPSILON; } };
|
|
template<> struct __approx_epsilon< float > { static constexpr float value() { return __APPROX_EPSILON * 4; } };
|
|
template< typename F > F approx_epsilon( F ) { return __approx_epsilon< F >::value(); }
|
|
/*
|
|
Note that this is still way smaller than the default!
|
|
|
|
|
|
How?
|
|
------
|
|
|
|
We provide our own functions to do approximate comparison:
|
|
REQUIRE( performComputation() == approx( 2.1 ));
|
|
*/
|
|
|
|
// Custom version of Approx, ==> better replaced by matchers <== for more control,
|
|
// but provides LRS defaults that should closely (but not exactly) match them
|
|
template< typename F >
|
|
inline Catch::Approx approx( F f )
|
|
{
|
|
return Catch::Approx( f )
|
|
.margin( __approx_margin< F >::value() )
|
|
.epsilon( __approx_epsilon< F >::value() );
|
|
}
|
|
|
|
/*
|
|
|
|
Literals
|
|
----------
|
|
|
|
Note that Catch has literals that make the syntax nice:
|
|
using namespace Catch::literals;
|
|
REQUIRE( performComputation() == 2.1_a );
|
|
Because we have our own implementatin (and because it's more verbose) we do NOT want
|
|
to use the literal that Catch supplies.
|
|
|
|
|
|
Matchers
|
|
----------
|
|
|
|
The above are good, but if you want more control, matchers provide a customizable
|
|
comparison:
|
|
REQUIRE_THAT( performComputation(), approx_equals( 2.1 ));
|
|
Or, for more control:
|
|
REQUIRE_THAT( performComputation(), approx_abs( 2.1 ));
|
|
REQUIRE_THAT( performComputation(), approx_rel( 2.1 ));
|
|
Or, with the Catch matchers, even more:
|
|
REQUIRE_THAT( performComputation(), WithinAbs( 2.1, 0.1 )); // 2.0 -> 2.2
|
|
REQUIRE_THAT( performComputation(), WithinRel( 2.1, 0.05 )); // 5% from 2.1
|
|
REQUIRE_THAT( performComputation(), WithinUlps( 2.1, 2 )); // two epsilons from 2.1
|
|
These matchers are type-sensitive (float vs. double).
|
|
*/
|
|
#define approx_abs(D) \
|
|
Catch::Matchers::WithinAbs( (D), approx_margin((D)) )
|
|
#define approx_rel(D) \
|
|
Catch::Matchers::WithinRel( (D), approx_epsilon((D)) )
|
|
#define approx_equals(D) \
|
|
( approx_abs(D) || approx_rel(D) )
|
|
|
|
|
|
// Utility function to help debug precision errors:
|
|
// INFO( full_precision( d ) );
|
|
// REQUIRE( 0.0 == d );
|
|
template< class T >
|
|
std::string full_precision( T const d )
|
|
{
|
|
std::ostringstream s;
|
|
s << std::setprecision( std::numeric_limits< T >::max_digits10 ) << d;
|
|
return s.str();
|
|
}
|
|
|