// (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 #include // Boost #include // for std::numeric_limits #include #include #include #include #include #include #include #include #include // STL #include #include //____________________________________________________________________________// 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 struct tolerance_based_delegate; template struct tolerance_based_delegate : mpl::false_ {}; // from https://stackoverflow.com/a/16509511/1617295 template class is_abstract_class_or_function { typedef char (&Two)[2]; template static char test(U(*)[1]); template static Two test(...); public: static const bool value = !is_reference::value && !is_void::value && (sizeof(test(0)) == sizeof(Two)); }; // warning: we cannot instanciate std::numeric_limits for incomplete types, we use is_abstract_class_or_function // prior to the specialization below template struct tolerance_based_delegate : mpl::bool_< is_floating_point::value || (!std::numeric_limits::is_integer && std::numeric_limits::is_specialized && !std::numeric_limits::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 struct tolerance_based : tolerance_based_delegate::value && !is_abstract_class_or_function::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 struct percent_tolerance_t { explicit percent_tolerance_t( FPT v ) : m_value( v ) {} FPT m_value; }; //____________________________________________________________________________// template inline std::ostream& operator<<( std::ostream& out, percent_tolerance_t t ) { return out << t.m_value; } //____________________________________________________________________________// template inline percent_tolerance_t percent_tolerance( FPT v ) { return percent_tolerance_t( v ); } //____________________________________________________________________________// // ************************************************************************** // // ************** details ************** // // ************************************************************************** // namespace fpc_detail { // FPT is Floating-Point Type: float, double, long double or User-Defined. template inline FPT fpt_abs( FPT fpv ) { return fpv < static_cast(0) ? -fpv : fpv; } //____________________________________________________________________________// template struct fpt_specialized_limits { static FPT min_value() { return (std::numeric_limits::min)(); } static FPT max_value() { return (std::numeric_limits::max)(); } }; template struct fpt_non_specialized_limits { static FPT min_value() { return static_cast(0); } static FPT max_value() { return static_cast(1000000); } // for our purposes it doesn't really matter what value is returned here }; template struct fpt_limits : boost::conditional::is_specialized, fpt_specialized_limits, fpt_non_specialized_limits >::type {}; //____________________________________________________________________________// // both f1 and f2 are unsigned here template inline FPT safe_fpt_division( FPT f1, FPT f2 ) { // Avoid overflow. if( (f2 < static_cast(1)) && (f1 > f2*fpt_limits::max_value()) ) return fpt_limits::max_value(); // Avoid underflow. if( (f1 == static_cast(0)) || ((f2 > static_cast(1)) && (f1 < f2*fpt_limits::min_value())) ) return static_cast(0); return f1/f2; } //____________________________________________________________________________// template inline FPT fraction_tolerance( ToleranceType tolerance ) { return static_cast(tolerance); } //____________________________________________________________________________// template inline FPT2 fraction_tolerance( percent_tolerance_t 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 difference, 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. * * This predicate is not suitable for comparing to 0 or to infinity. */ template class close_at_tolerance { public: // Public typedefs typedef bool result_type; // Constructor template explicit close_at_tolerance( ToleranceType tolerance, fpc::strength fpc_strength = FPC_STRONG ) : m_fraction_tolerance( fpc_detail::fraction_tolerance( 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( 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 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 inline bool is_small( FPT fpv, FPT tolerance ) { return small_with_tolerance( tolerance )( fpv ); } //____________________________________________________________________________// } // namespace fpc } // namespace math } // namespace boost #include #endif // BOOST_FLOATING_POINT_COMAPARISON_HPP_071894GER