summaryrefslogtreecommitdiff
path: root/boost/test/tools/floating_point_comparison.hpp
blob: d704a41092c8277fa5af8df3e51cb74c445970a6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
//  (C) Copyright Gennadiy Rozental 2001.
//  Distributed under the Boost Software License, Version 1.0.
//  (See accompanying file LICENSE_1_0.txt or copy at 
//  http://www.boost.org/LICENSE_1_0.txt)

//  See http://www.boost.org/libs/test for the library home page.
//
//!@file 
//!@brief algorithms for comparing floating point values
// ***************************************************************************

#ifndef BOOST_TEST_FLOATING_POINT_COMPARISON_HPP_071894GER
#define BOOST_TEST_FLOATING_POINT_COMPARISON_HPP_071894GER

// Boost.Test
#include <boost/test/detail/global_typedef.hpp>
#include <boost/test/tools/assertion_result.hpp>

// Boost
#include <boost/limits.hpp>  // for std::numeric_limits
#include <boost/static_assert.hpp>
#include <boost/assert.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/type_traits/is_floating_point.hpp>
#include <boost/type_traits/is_array.hpp>
#include <boost/type_traits/conditional.hpp>
#include <boost/utility/enable_if.hpp>

// STL
#include <iosfwd>

#include <boost/test/detail/suppress_warnings.hpp>

//____________________________________________________________________________//

namespace boost {
namespace math {
namespace fpc {

// ************************************************************************** //
// **************              fpc::tolerance_based            ************** //
// ************************************************************************** //


//! @internal
//! Protects the instanciation of std::numeric_limits from non-supported types (eg. T=array)
template <typename T, bool enabled>
struct tolerance_based_delegate;

template <typename T>
struct tolerance_based_delegate<T, false> : mpl::false_ {};

template <typename T>
struct tolerance_based_delegate<T, true>
: mpl::bool_<
    is_floating_point<T>::value ||
    (!std::numeric_limits<T>::is_integer && std::numeric_limits<T>::is_specialized && !std::numeric_limits<T>::is_exact)>
{};


/*!@brief Indicates if a type can be compared using a tolerance scheme
 *
 * This is a metafunction that should evaluate to @c mpl::true_ if the type
 * @c T can be compared using a tolerance based method, typically for floating point
 * types.
 *
 * This metafunction can be specialized further to declare user types that are
 * floating point (eg. boost.multiprecision).
 */
template <typename T>
struct tolerance_based : tolerance_based_delegate<T, !is_array<T>::value >::type {};

// ************************************************************************** //
// **************                 fpc::strength                ************** //
// ************************************************************************** //

//! Method for comparing floating point numbers
enum strength {
    FPC_STRONG, //!< "Very close"   - equation 2' in docs, the default
    FPC_WEAK    //!< "Close enough" - equation 3' in docs.
};


// ************************************************************************** //
// **************         tolerance presentation types         ************** //
// ************************************************************************** //

template<typename FPT>
struct percent_tolerance_t {
    explicit    percent_tolerance_t( FPT v ) : m_value( v ) {}

    FPT m_value;
};

//____________________________________________________________________________//

template<typename FPT>
inline std::ostream& operator<<( std::ostream& out, percent_tolerance_t<FPT> t )
{
    return out << t.m_value;
}

//____________________________________________________________________________//

template<typename FPT>
inline percent_tolerance_t<FPT>
percent_tolerance( FPT v )
{
    return percent_tolerance_t<FPT>( v );
}

//____________________________________________________________________________//

// ************************************************************************** //
// **************                    details                   ************** //
// ************************************************************************** //

namespace fpc_detail {

// FPT is Floating-Point Type: float, double, long double or User-Defined.
template<typename FPT>
inline FPT
fpt_abs( FPT fpv ) 
{
    return fpv < static_cast<FPT>(0) ? -fpv : fpv;
}

//____________________________________________________________________________//

template<typename FPT>
struct fpt_specialized_limits
{
  static FPT    min_value() { return (std::numeric_limits<FPT>::min)(); }
  static FPT    max_value() { return (std::numeric_limits<FPT>::max)(); }
};

template<typename FPT>
struct fpt_non_specialized_limits
{
  static FPT    min_value() { return static_cast<FPT>(0); }
  static FPT    max_value() { return static_cast<FPT>(1000000); } // for our purposes it doesn't really matter what value is returned here
};

template<typename FPT>
struct fpt_limits : boost::conditional<std::numeric_limits<FPT>::is_specialized,
                                       fpt_specialized_limits<FPT>,
                                       fpt_non_specialized_limits<FPT>
                                      >::type
{};

//____________________________________________________________________________//

// both f1 and f2 are unsigned here
template<typename FPT>
inline FPT
safe_fpt_division( FPT f1, FPT f2 )
{
    // Avoid overflow.
    if( (f2 < static_cast<FPT>(1))  && (f1 > f2*fpt_limits<FPT>::max_value()) )
        return fpt_limits<FPT>::max_value();

    // Avoid underflow.
    if( (f1 == static_cast<FPT>(0)) ||
        ((f2 > static_cast<FPT>(1)) && (f1 < f2*fpt_limits<FPT>::min_value())) )
        return static_cast<FPT>(0);

    return f1/f2;
}

//____________________________________________________________________________//

template<typename FPT, typename ToleranceType>
inline FPT
fraction_tolerance( ToleranceType tolerance )
{
  return static_cast<FPT>(tolerance);
} 

//____________________________________________________________________________//

template<typename FPT2, typename FPT>
inline FPT2
fraction_tolerance( percent_tolerance_t<FPT> tolerance )
{
    return FPT2(tolerance.m_value)*FPT2(0.01); 
}

//____________________________________________________________________________//

} // namespace fpc_detail

// ************************************************************************** //
// **************             close_at_tolerance               ************** //
// ************************************************************************** //


/*!@brief Predicate for comparing floating point numbers
 *
 * This predicate is used to compare floating point numbers. In addition the comparison produces maximum 
 * related differnce, which can be used to generate detailed error message
 * The methods for comparing floating points are detailed in the documentation. The method is chosen
 * by the @ref boost::math::fpc::strength given at construction.
 */
template<typename FPT>
class close_at_tolerance {
public:
    // Public typedefs
    typedef bool result_type;

    // Constructor
    template<typename ToleranceType>
    explicit    close_at_tolerance( ToleranceType tolerance, fpc::strength fpc_strength = FPC_STRONG ) 
    : m_fraction_tolerance( fpc_detail::fraction_tolerance<FPT>( tolerance ) )
    , m_strength( fpc_strength )
    , m_tested_rel_diff( 0 )
    {
        BOOST_ASSERT_MSG( m_fraction_tolerance >= FPT(0), "tolerance must not be negative!" ); // no reason for tolerance to be negative
    }

    // Access methods
    //! Returns the tolerance
    FPT                 fraction_tolerance() const  { return m_fraction_tolerance; }

    //! Returns the comparison method
    fpc::strength       strength() const            { return m_strength; }

    //! Returns the failing fraction
    FPT                 tested_rel_diff() const     { return m_tested_rel_diff; }

    /*! Compares two floating point numbers a and b such that their "left" relative difference |a-b|/a and/or
     * "right" relative difference |a-b|/b does not exceed specified relative (fraction) tolerance.
     *
     *  @param[in] left first floating point number to be compared
     *  @param[in] right second floating point number to be compared
     *
     * What is reported by @c tested_rel_diff in case of failure depends on the comparison method:
     * - for @c FPC_STRONG: the max of the two fractions
     * - for @c FPC_WEAK: the min of the two fractions
     * The rationale behind is to report the tolerance to set in order to make a test pass.
     */
    bool                operator()( FPT left, FPT right ) const
    {
        FPT diff              = fpc_detail::fpt_abs<FPT>( left - right );
        FPT fraction_of_right = fpc_detail::safe_fpt_division( diff, fpc_detail::fpt_abs( right ) );
        FPT fraction_of_left  = fpc_detail::safe_fpt_division( diff, fpc_detail::fpt_abs( left ) );

        FPT max_rel_diff = (std::max)( fraction_of_left, fraction_of_right );
        FPT min_rel_diff = (std::min)( fraction_of_left, fraction_of_right );

        m_tested_rel_diff = m_strength == FPC_STRONG ? max_rel_diff : min_rel_diff;

        return m_tested_rel_diff <= m_fraction_tolerance;
    }

private:
    // Data members
    FPT                 m_fraction_tolerance;
    fpc::strength       m_strength;
    mutable FPT         m_tested_rel_diff;
};

// ************************************************************************** //
// **************            small_with_tolerance              ************** //
// ************************************************************************** //


/*!@brief Predicate for comparing floating point numbers against 0
 *
 * Serves the same purpose as boost::math::fpc::close_at_tolerance, but used when one
 * of the operand is null. 
 */
template<typename FPT>
class small_with_tolerance {
public:
    // Public typedefs
    typedef bool result_type;

    // Constructor
    explicit    small_with_tolerance( FPT tolerance ) // <= absolute tolerance
    : m_tolerance( tolerance )
    {
        BOOST_ASSERT( m_tolerance >= FPT(0) ); // no reason for the tolerance to be negative
    }

    // Action method
    bool        operator()( FPT fpv ) const
    {
        return fpc::fpc_detail::fpt_abs( fpv ) <= m_tolerance;
    }

private:
    // Data members
    FPT         m_tolerance;
};

// ************************************************************************** //
// **************                  is_small                    ************** //
// ************************************************************************** //

template<typename FPT>
inline bool
is_small( FPT fpv, FPT tolerance )
{
    return small_with_tolerance<FPT>( tolerance )( fpv );
}

//____________________________________________________________________________//

} // namespace fpc
} // namespace math
} // namespace boost

#include <boost/test/detail/enable_warnings.hpp>

#endif // BOOST_FLOATING_POINT_COMAPARISON_HPP_071894GER