238 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			238 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			C++
		
	
	
	
| /*
 | |
|  *  Created by Martin on 07/11/2017.
 | |
|  *
 | |
|  * 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)
 | |
|  */
 | |
| 
 | |
| #include "catch_matchers_floating.h"
 | |
| #include "catch_enforce.h"
 | |
| #include "catch_polyfills.hpp"
 | |
| #include "catch_to_string.hpp"
 | |
| #include "catch_tostring.h"
 | |
| 
 | |
| #include <algorithm>
 | |
| #include <cmath>
 | |
| #include <cstdlib>
 | |
| #include <cstdint>
 | |
| #include <cstring>
 | |
| #include <sstream>
 | |
| #include <type_traits>
 | |
| #include <iomanip>
 | |
| #include <limits>
 | |
| 
 | |
| 
 | |
| namespace Catch {
 | |
| namespace {
 | |
| 
 | |
|     int32_t convert(float f) {
 | |
|         static_assert(sizeof(float) == sizeof(int32_t), "Important ULP matcher assumption violated");
 | |
|         int32_t i;
 | |
|         std::memcpy(&i, &f, sizeof(f));
 | |
|         return i;
 | |
|     }
 | |
| 
 | |
|     int64_t convert(double d) {
 | |
|         static_assert(sizeof(double) == sizeof(int64_t), "Important ULP matcher assumption violated");
 | |
|         int64_t i;
 | |
|         std::memcpy(&i, &d, sizeof(d));
 | |
|         return i;
 | |
|     }
 | |
| 
 | |
|     template <typename FP>
 | |
|     bool almostEqualUlps(FP lhs, FP rhs, uint64_t maxUlpDiff) {
 | |
|         // Comparison with NaN should always be false.
 | |
|         // This way we can rule it out before getting into the ugly details
 | |
|         if (Catch::isnan(lhs) || Catch::isnan(rhs)) {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         auto lc = convert(lhs);
 | |
|         auto rc = convert(rhs);
 | |
| 
 | |
|         if ((lc < 0) != (rc < 0)) {
 | |
|             // Potentially we can have +0 and -0
 | |
|             return lhs == rhs;
 | |
|         }
 | |
| 
 | |
|         auto ulpDiff = std::abs(lc - rc);
 | |
|         return static_cast<uint64_t>(ulpDiff) <= maxUlpDiff;
 | |
|     }
 | |
| 
 | |
| #if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER)
 | |
| 
 | |
|     float nextafter(float x, float y) {
 | |
|         return ::nextafterf(x, y);
 | |
|     }
 | |
| 
 | |
|     double nextafter(double x, double y) {
 | |
|         return ::nextafter(x, y);
 | |
|     }
 | |
| 
 | |
| #endif // ^^^ CATCH_CONFIG_GLOBAL_NEXTAFTER ^^^
 | |
| 
 | |
| template <typename FP>
 | |
| FP step(FP start, FP direction, uint64_t steps) {
 | |
|     for (uint64_t i = 0; i < steps; ++i) {
 | |
| #if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER)
 | |
|         start = Catch::nextafter(start, direction);
 | |
| #else
 | |
|         start = std::nextafter(start, direction);
 | |
| #endif
 | |
|     }
 | |
|     return start;
 | |
| }
 | |
| 
 | |
| // 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);
 | |
| }
 | |
| 
 | |
| template <typename FloatingPoint>
 | |
| void write(std::ostream& out, FloatingPoint num) {
 | |
|     out << std::scientific
 | |
|         << std::setprecision(std::numeric_limits<FloatingPoint>::max_digits10 - 1)
 | |
|         << num;
 | |
| }
 | |
| 
 | |
| } // end anonymous namespace
 | |
| 
 | |
| namespace Matchers {
 | |
| namespace Floating {
 | |
| 
 | |
|     enum class FloatingPointKind : uint8_t {
 | |
|         Float,
 | |
|         Double
 | |
|     };
 | |
| 
 | |
| 
 | |
|     WithinAbsMatcher::WithinAbsMatcher(double target, double margin)
 | |
|         :m_target{ target }, m_margin{ margin } {
 | |
|         CATCH_ENFORCE(margin >= 0, "Invalid margin: " << margin << '.'
 | |
|             << " Margin has to be non-negative.");
 | |
|     }
 | |
| 
 | |
|     // Performs equivalent check of std::fabs(lhs - rhs) <= margin
 | |
|     // But without the subtraction to allow for INFINITY in comparison
 | |
|     bool WithinAbsMatcher::match(double const& matchee) const {
 | |
|         return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee);
 | |
|     }
 | |
| 
 | |
|     std::string WithinAbsMatcher::describe() const {
 | |
|         return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target);
 | |
|     }
 | |
| 
 | |
| 
 | |
|     WithinUlpsMatcher::WithinUlpsMatcher(double target, uint64_t ulps, FloatingPointKind baseType)
 | |
|         :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } {
 | |
|         CATCH_ENFORCE(m_type == FloatingPointKind::Double
 | |
|                    || m_ulps < (std::numeric_limits<uint32_t>::max)(),
 | |
|             "Provided ULP is impossibly large for a float comparison.");
 | |
|     }
 | |
| 
 | |
| #if defined(__clang__)
 | |
| #pragma clang diagnostic push
 | |
| // Clang <3.5 reports on the default branch in the switch below
 | |
| #pragma clang diagnostic ignored "-Wunreachable-code"
 | |
| #endif
 | |
| 
 | |
|     bool WithinUlpsMatcher::match(double const& matchee) const {
 | |
|         switch (m_type) {
 | |
|         case FloatingPointKind::Float:
 | |
|             return almostEqualUlps<float>(static_cast<float>(matchee), static_cast<float>(m_target), m_ulps);
 | |
|         case FloatingPointKind::Double:
 | |
|             return almostEqualUlps<double>(matchee, m_target, m_ulps);
 | |
|         default:
 | |
|             CATCH_INTERNAL_ERROR( "Unknown FloatingPointKind value" );
 | |
|         }
 | |
|     }
 | |
| 
 | |
| #if defined(__clang__)
 | |
| #pragma clang diagnostic pop
 | |
| #endif
 | |
| 
 | |
|     std::string WithinUlpsMatcher::describe() const {
 | |
|         std::stringstream ret;
 | |
| 
 | |
|         ret << "is within " << m_ulps << " ULPs of ";
 | |
| 
 | |
|         if (m_type == FloatingPointKind::Float) {
 | |
|             write(ret, static_cast<float>(m_target));
 | |
|             ret << 'f';
 | |
|         } else {
 | |
|             write(ret, m_target);
 | |
|         }
 | |
| 
 | |
|         ret << " ([";
 | |
|         if (m_type == FloatingPointKind::Double) {
 | |
|             write(ret, step(m_target, static_cast<double>(-INFINITY), m_ulps));
 | |
|             ret << ", ";
 | |
|             write(ret, step(m_target, static_cast<double>( INFINITY), m_ulps));
 | |
|         } else {
 | |
|             // We have to cast INFINITY to float because of MinGW, see #1782
 | |
|             write(ret, step(static_cast<float>(m_target), static_cast<float>(-INFINITY), m_ulps));
 | |
|             ret << ", ";
 | |
|             write(ret, step(static_cast<float>(m_target), static_cast<float>( INFINITY), m_ulps));
 | |
|         }
 | |
|         ret << "])";
 | |
| 
 | |
|         return ret.str();
 | |
|     }
 | |
| 
 | |
|     WithinRelMatcher::WithinRelMatcher(double target, double epsilon):
 | |
|         m_target(target),
 | |
|         m_epsilon(epsilon){
 | |
|         CATCH_ENFORCE(m_epsilon >= 0., "Relative comparison with epsilon <  0 does not make sense.");
 | |
|         CATCH_ENFORCE(m_epsilon  < 1., "Relative comparison with epsilon >= 1 does not make sense.");
 | |
|     }
 | |
| 
 | |
|     bool WithinRelMatcher::match(double const& matchee) const {
 | |
|         const auto relMargin = m_epsilon * (std::max)(std::fabs(matchee), std::fabs(m_target));
 | |
|         return marginComparison(matchee, m_target,
 | |
|                                 std::isinf(relMargin)? 0 : relMargin);
 | |
|     }
 | |
| 
 | |
|     std::string WithinRelMatcher::describe() const {
 | |
|         Catch::ReusableStringStream sstr;
 | |
|         sstr << "and " << m_target << " are within " << m_epsilon * 100. << "% of each other";
 | |
|         return sstr.str();
 | |
|     }
 | |
| 
 | |
| }// namespace Floating
 | |
| 
 | |
| 
 | |
| 
 | |
| Floating::WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff) {
 | |
|     return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Double);
 | |
| }
 | |
| 
 | |
| Floating::WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff) {
 | |
|     return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Float);
 | |
| }
 | |
| 
 | |
| Floating::WithinAbsMatcher WithinAbs(double target, double margin) {
 | |
|     return Floating::WithinAbsMatcher(target, margin);
 | |
| }
 | |
| 
 | |
| Floating::WithinRelMatcher WithinRel(double target, double eps) {
 | |
|     return Floating::WithinRelMatcher(target, eps);
 | |
| }
 | |
| 
 | |
| Floating::WithinRelMatcher WithinRel(double target) {
 | |
|     return Floating::WithinRelMatcher(target, std::numeric_limits<double>::epsilon() * 100);
 | |
| }
 | |
| 
 | |
| Floating::WithinRelMatcher WithinRel(float target, float eps) {
 | |
|     return Floating::WithinRelMatcher(target, eps);
 | |
| }
 | |
| 
 | |
| Floating::WithinRelMatcher WithinRel(float target) {
 | |
|     return Floating::WithinRelMatcher(target, std::numeric_limits<float>::epsilon() * 100);
 | |
| }
 | |
| 
 | |
| 
 | |
| } // namespace Matchers
 | |
| } // namespace Catch
 | |
| 
 |