2017-11-10 18:14:42 +01:00
|
|
|
/*
|
|
|
|
* 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"
|
2018-09-03 10:03:47 +02:00
|
|
|
#include "catch_enforce.h"
|
2018-11-17 20:52:18 +01:00
|
|
|
#include "catch_polyfills.hpp"
|
2018-05-09 20:16:27 +02:00
|
|
|
#include "catch_to_string.hpp"
|
2017-11-10 21:43:23 +01:00
|
|
|
#include "catch_tostring.h"
|
2017-11-10 18:14:42 +01:00
|
|
|
|
2019-10-13 11:56:50 +02:00
|
|
|
#include <algorithm>
|
2019-09-06 13:08:44 +02:00
|
|
|
#include <cmath>
|
2017-11-10 21:43:23 +01:00
|
|
|
#include <cstdlib>
|
2017-11-10 18:14:42 +01:00
|
|
|
#include <cstdint>
|
|
|
|
#include <cstring>
|
2019-06-14 20:16:12 +02:00
|
|
|
#include <sstream>
|
2019-09-06 13:08:44 +02:00
|
|
|
#include <type_traits>
|
2019-06-14 20:16:12 +02:00
|
|
|
#include <iomanip>
|
|
|
|
#include <limits>
|
2017-11-10 18:14:42 +01:00
|
|
|
|
2019-09-06 13:08:44 +02:00
|
|
|
|
2017-11-10 18:14:42 +01:00
|
|
|
namespace Catch {
|
|
|
|
namespace {
|
|
|
|
|
2019-10-05 20:58:11 +02:00
|
|
|
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;
|
|
|
|
}
|
2017-11-10 18:14:42 +01:00
|
|
|
|
2019-10-05 20:58:11 +02:00
|
|
|
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;
|
2017-11-10 18:14:42 +01:00
|
|
|
}
|
|
|
|
|
2019-10-05 20:58:11 +02:00
|
|
|
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;
|
|
|
|
}
|
2017-11-10 18:14:42 +01:00
|
|
|
|
2019-10-05 20:58:11 +02:00
|
|
|
auto lc = convert(lhs);
|
|
|
|
auto rc = convert(rhs);
|
|
|
|
|
|
|
|
if ((lc < 0) != (rc < 0)) {
|
|
|
|
// Potentially we can have +0 and -0
|
|
|
|
return lhs == rhs;
|
|
|
|
}
|
2017-11-10 18:14:42 +01:00
|
|
|
|
2019-10-05 20:58:11 +02:00
|
|
|
auto ulpDiff = std::abs(lc - rc);
|
|
|
|
return static_cast<uint64_t>(ulpDiff) <= maxUlpDiff;
|
|
|
|
}
|
2019-10-13 11:56:50 +02:00
|
|
|
|
|
|
|
} //end anonymous namespace
|
2017-11-10 18:14:42 +01:00
|
|
|
|
2019-09-06 13:08:44 +02:00
|
|
|
#if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER)
|
|
|
|
|
2019-10-05 20:58:11 +02:00
|
|
|
#if defined(__clang__)
|
|
|
|
#pragma clang diagnostic push
|
|
|
|
// The long double overload is currently unused
|
|
|
|
#pragma clang diagnostic ignored "-Wunused-function"
|
|
|
|
#endif
|
|
|
|
|
2019-09-06 13:08:44 +02:00
|
|
|
float nextafter(float x, float y) {
|
|
|
|
return ::nextafterf(x, y);
|
|
|
|
}
|
|
|
|
|
|
|
|
double nextafter(double x, double y) {
|
|
|
|
return ::nextafter(x, y);
|
|
|
|
}
|
|
|
|
|
|
|
|
long double nextafter(long double x, long double y) {
|
|
|
|
return ::nextafterl(x, y);
|
|
|
|
}
|
|
|
|
|
2019-10-05 20:58:11 +02:00
|
|
|
#if defined(__clang__)
|
|
|
|
#pragma clang diagnostic pop
|
2019-09-06 13:08:44 +02:00
|
|
|
#endif
|
|
|
|
|
2019-10-05 20:58:11 +02:00
|
|
|
#endif // ^^^ CATCH_CONFIG_GLOBAL_NEXTAFTER ^^^
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
2019-06-14 20:16:12 +02:00
|
|
|
template <typename FP>
|
2019-10-05 13:23:14 +02:00
|
|
|
FP step(FP start, FP direction, uint64_t steps) {
|
|
|
|
for (uint64_t i = 0; i < steps; ++i) {
|
2019-09-06 13:08:44 +02:00
|
|
|
#if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER)
|
|
|
|
start = Catch::nextafter(start, direction);
|
|
|
|
#else
|
2019-06-14 20:16:12 +02:00
|
|
|
start = std::nextafter(start, direction);
|
2019-09-06 13:08:44 +02:00
|
|
|
#endif
|
2019-06-14 20:16:12 +02:00
|
|
|
}
|
|
|
|
return start;
|
2017-11-10 18:14:42 +01:00
|
|
|
}
|
2019-10-13 11:56:50 +02:00
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-06-14 20:16:12 +02:00
|
|
|
} // end anonymous namespace
|
|
|
|
|
2017-11-10 18:14:42 +01:00
|
|
|
namespace Matchers {
|
|
|
|
namespace Floating {
|
2019-10-05 20:58:11 +02:00
|
|
|
|
|
|
|
enum class FloatingPointKind : uint8_t {
|
|
|
|
Float,
|
|
|
|
Double
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2017-11-10 18:14:42 +01:00
|
|
|
WithinAbsMatcher::WithinAbsMatcher(double target, double margin)
|
2017-12-06 15:37:13 +01:00
|
|
|
:m_target{ target }, m_margin{ margin } {
|
2018-09-03 10:03:47 +02:00
|
|
|
CATCH_ENFORCE(margin >= 0, "Invalid margin: " << margin << '.'
|
|
|
|
<< " Margin has to be non-negative.");
|
2017-12-06 15:37:13 +01:00
|
|
|
}
|
2017-11-10 18:14:42 +01:00
|
|
|
|
|
|
|
// 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 {
|
2018-03-19 20:36:07 +01:00
|
|
|
return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee);
|
2017-11-10 18:14:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string WithinAbsMatcher::describe() const {
|
2017-11-10 21:43:23 +01:00
|
|
|
return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target);
|
2017-11-10 18:14:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-10-05 13:23:14 +02:00
|
|
|
WithinUlpsMatcher::WithinUlpsMatcher(double target, uint64_t ulps, FloatingPointKind baseType)
|
2017-11-10 18:14:42 +01:00
|
|
|
:m_target{ target }, m_ulps{ ulps }, m_type{ baseType } {
|
2019-10-05 13:23:14 +02:00
|
|
|
CATCH_ENFORCE(m_type == FloatingPointKind::Double
|
2019-10-17 11:21:17 +02:00
|
|
|
|| m_ulps < (std::numeric_limits<uint32_t>::max)(),
|
2019-10-05 13:23:14 +02:00
|
|
|
"Provided ULP is impossibly large for a float comparison.");
|
2017-11-10 18:14:42 +01:00
|
|
|
}
|
|
|
|
|
2018-09-01 22:34:29 +02:00
|
|
|
#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
|
|
|
|
|
2017-11-10 18:14:42 +01:00
|
|
|
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:
|
2018-09-03 10:03:47 +02:00
|
|
|
CATCH_INTERNAL_ERROR( "Unknown FloatingPointKind value" );
|
2017-11-10 18:14:42 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-01 22:34:29 +02:00
|
|
|
#if defined(__clang__)
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
#endif
|
|
|
|
|
2017-11-10 18:14:42 +01:00
|
|
|
std::string WithinUlpsMatcher::describe() const {
|
2019-06-14 20:16:12 +02:00
|
|
|
std::stringstream ret;
|
|
|
|
|
|
|
|
ret << "is within " << m_ulps << " ULPs of " << ::Catch::Detail::stringify(m_target);
|
|
|
|
|
|
|
|
if (m_type == FloatingPointKind::Float) {
|
|
|
|
ret << 'f';
|
|
|
|
}
|
|
|
|
|
|
|
|
ret << " ([";
|
|
|
|
ret << std::fixed << std::setprecision(std::numeric_limits<double>::max_digits10);
|
|
|
|
if (m_type == FloatingPointKind::Double) {
|
|
|
|
ret << step(m_target, static_cast<double>(-INFINITY), m_ulps)
|
|
|
|
<< ", "
|
|
|
|
<< step(m_target, static_cast<double>(INFINITY), m_ulps);
|
|
|
|
} else {
|
|
|
|
ret << step<float>(static_cast<float>(m_target), -INFINITY, m_ulps)
|
|
|
|
<< ", "
|
|
|
|
<< step<float>(static_cast<float>(m_target), INFINITY, m_ulps);
|
|
|
|
}
|
|
|
|
ret << "])";
|
|
|
|
|
|
|
|
return ret.str();
|
|
|
|
//return "is within " + Catch::to_string(m_ulps) + " ULPs of " + ::Catch::Detail::stringify(m_target) + ((m_type == FloatingPointKind::Float)? "f" : "");
|
2017-11-10 18:14:42 +01:00
|
|
|
}
|
|
|
|
|
2019-10-13 11:56:50 +02:00
|
|
|
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 {
|
2019-10-17 11:21:17 +02:00
|
|
|
const auto relMargin = m_epsilon * (std::max)(std::fabs(matchee), std::fabs(m_target));
|
2019-10-13 11:56:50 +02:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2017-11-10 18:14:42 +01:00
|
|
|
}// namespace Floating
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-10-05 13:23:14 +02:00
|
|
|
Floating::WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff) {
|
2017-11-10 18:14:42 +01:00
|
|
|
return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Double);
|
|
|
|
}
|
|
|
|
|
2019-10-05 13:23:14 +02:00
|
|
|
Floating::WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff) {
|
2017-11-10 18:14:42 +01:00
|
|
|
return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Float);
|
|
|
|
}
|
|
|
|
|
|
|
|
Floating::WithinAbsMatcher WithinAbs(double target, double margin) {
|
|
|
|
return Floating::WithinAbsMatcher(target, margin);
|
|
|
|
}
|
|
|
|
|
2019-10-13 11:56:50 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-10 18:14:42 +01:00
|
|
|
} // namespace Matchers
|
|
|
|
} // namespace Catch
|
|
|
|
|