catch2/include/internal/catch_matchers_floating.cpp
Martin Hořeňovský c38a5caa2e
Allow full range of target ULP values for the ULPMatcher
Previously it was limited to roughly 2 billion ULPs, rather than
the roughly 2^64 possible ones.
2019-10-05 13:38:22 +02:00

197 lines
5.6 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 <cmath>
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <sstream>
#include <type_traits>
#include <iomanip>
#include <limits>
namespace Catch {
namespace Matchers {
namespace Floating {
enum class FloatingPointKind : uint8_t {
Float,
Double
};
}
}
}
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)
namespace Catch {
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);
}
} // end namespace Catch
#endif
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;
}
} // end anonymous namespace
namespace Catch {
namespace Matchers {
namespace Floating {
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 " << ::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" : "");
}
}// 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);
}
} // namespace Matchers
} // namespace Catch