1 /*
2 * Created by Martin on 07/11/2017.
3 *
4 * Distributed under the Boost Software License, Version 1.0. (See accompanying
5 * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 */
7
8 #include "catch_matchers_floating.h"
9 #include "catch_enforce.h"
10 #include "catch_polyfills.hpp"
11 #include "catch_to_string.hpp"
12 #include "catch_tostring.h"
13
14 #include <cstdlib>
15 #include <cstdint>
16 #include <cstring>
17
18 namespace Catch {
19 namespace Matchers {
20 namespace Floating {
21 enum class FloatingPointKind : uint8_t {
22 Float,
23 Double
24 };
25 }
26 }
27 }
28
29 namespace {
30
31 template <typename T>
32 struct Converter;
33
34 template <>
35 struct Converter<float> {
36 static_assert(sizeof(float) == sizeof(int32_t), "Important ULP matcher assumption violated");
Converter__anon82af003a0111::Converter37 Converter(float f) {
38 std::memcpy(&i, &f, sizeof(f));
39 }
40 int32_t i;
41 };
42
43 template <>
44 struct Converter<double> {
45 static_assert(sizeof(double) == sizeof(int64_t), "Important ULP matcher assumption violated");
Converter__anon82af003a0111::Converter46 Converter(double d) {
47 std::memcpy(&i, &d, sizeof(d));
48 }
49 int64_t i;
50 };
51
52 template <typename T>
convert(T t)53 auto convert(T t) -> Converter<T> {
54 return Converter<T>(t);
55 }
56
57 template <typename FP>
almostEqualUlps(FP lhs,FP rhs,int maxUlpDiff)58 bool almostEqualUlps(FP lhs, FP rhs, int maxUlpDiff) {
59 // Comparison with NaN should always be false.
60 // This way we can rule it out before getting into the ugly details
61 if (Catch::isnan(lhs) || Catch::isnan(rhs)) {
62 return false;
63 }
64
65 auto lc = convert(lhs);
66 auto rc = convert(rhs);
67
68 if ((lc.i < 0) != (rc.i < 0)) {
69 // Potentially we can have +0 and -0
70 return lhs == rhs;
71 }
72
73 auto ulpDiff = std::abs(lc.i - rc.i);
74 return ulpDiff <= maxUlpDiff;
75 }
76
77 }
78
79
80 namespace Catch {
81 namespace Matchers {
82 namespace Floating {
WithinAbsMatcher(double target,double margin)83 WithinAbsMatcher::WithinAbsMatcher(double target, double margin)
84 :m_target{ target }, m_margin{ margin } {
85 CATCH_ENFORCE(margin >= 0, "Invalid margin: " << margin << '.'
86 << " Margin has to be non-negative.");
87 }
88
89 // Performs equivalent check of std::fabs(lhs - rhs) <= margin
90 // But without the subtraction to allow for INFINITY in comparison
match(double const & matchee) const91 bool WithinAbsMatcher::match(double const& matchee) const {
92 return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee);
93 }
94
describe() const95 std::string WithinAbsMatcher::describe() const {
96 return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target);
97 }
98
99
WithinUlpsMatcher(double target,int ulps,FloatingPointKind baseType)100 WithinUlpsMatcher::WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType)
101 :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } {
102 CATCH_ENFORCE(ulps >= 0, "Invalid ULP setting: " << ulps << '.'
103 << " ULPs have to be non-negative.");
104 }
105
106 #if defined(__clang__)
107 #pragma clang diagnostic push
108 // Clang <3.5 reports on the default branch in the switch below
109 #pragma clang diagnostic ignored "-Wunreachable-code"
110 #endif
111
match(double const & matchee) const112 bool WithinUlpsMatcher::match(double const& matchee) const {
113 switch (m_type) {
114 case FloatingPointKind::Float:
115 return almostEqualUlps<float>(static_cast<float>(matchee), static_cast<float>(m_target), m_ulps);
116 case FloatingPointKind::Double:
117 return almostEqualUlps<double>(matchee, m_target, m_ulps);
118 default:
119 CATCH_INTERNAL_ERROR( "Unknown FloatingPointKind value" );
120 }
121 }
122
123 #if defined(__clang__)
124 #pragma clang diagnostic pop
125 #endif
126
describe() const127 std::string WithinUlpsMatcher::describe() const {
128 return "is within " + Catch::to_string(m_ulps) + " ULPs of " + ::Catch::Detail::stringify(m_target) + ((m_type == FloatingPointKind::Float)? "f" : "");
129 }
130
131 }// namespace Floating
132
133
134
WithinULP(double target,int maxUlpDiff)135 Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff) {
136 return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Double);
137 }
138
WithinULP(float target,int maxUlpDiff)139 Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff) {
140 return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Float);
141 }
142
WithinAbs(double target,double margin)143 Floating::WithinAbsMatcher WithinAbs(double target, double margin) {
144 return Floating::WithinAbsMatcher(target, margin);
145 }
146
147 } // namespace Matchers
148 } // namespace Catch
149
150