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 <algorithm>
15 #include <cmath>
16 #include <cstdlib>
17 #include <cstdint>
18 #include <cstring>
19 #include <sstream>
20 #include <type_traits>
21 #include <iomanip>
22 #include <limits>
23
24
25 namespace Catch {
26 namespace {
27
convert(float f)28 int32_t convert(float f) {
29 static_assert(sizeof(float) == sizeof(int32_t), "Important ULP matcher assumption violated");
30 int32_t i;
31 std::memcpy(&i, &f, sizeof(f));
32 return i;
33 }
34
convert(double d)35 int64_t convert(double d) {
36 static_assert(sizeof(double) == sizeof(int64_t), "Important ULP matcher assumption violated");
37 int64_t i;
38 std::memcpy(&i, &d, sizeof(d));
39 return i;
40 }
41
42 template <typename FP>
almostEqualUlps(FP lhs,FP rhs,uint64_t maxUlpDiff)43 bool almostEqualUlps(FP lhs, FP rhs, uint64_t maxUlpDiff) {
44 // Comparison with NaN should always be false.
45 // This way we can rule it out before getting into the ugly details
46 if (Catch::isnan(lhs) || Catch::isnan(rhs)) {
47 return false;
48 }
49
50 auto lc = convert(lhs);
51 auto rc = convert(rhs);
52
53 if ((lc < 0) != (rc < 0)) {
54 // Potentially we can have +0 and -0
55 return lhs == rhs;
56 }
57
58 auto ulpDiff = std::abs(lc - rc);
59 return static_cast<uint64_t>(ulpDiff) <= maxUlpDiff;
60 }
61
62 #if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER)
63
nextafter(float x,float y)64 float nextafter(float x, float y) {
65 return ::nextafterf(x, y);
66 }
67
nextafter(double x,double y)68 double nextafter(double x, double y) {
69 return ::nextafter(x, y);
70 }
71
72 #endif // ^^^ CATCH_CONFIG_GLOBAL_NEXTAFTER ^^^
73
74 template <typename FP>
step(FP start,FP direction,uint64_t steps)75 FP step(FP start, FP direction, uint64_t steps) {
76 for (uint64_t i = 0; i < steps; ++i) {
77 #if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER)
78 start = Catch::nextafter(start, direction);
79 #else
80 start = std::nextafter(start, direction);
81 #endif
82 }
83 return start;
84 }
85
86 // Performs equivalent check of std::fabs(lhs - rhs) <= margin
87 // But without the subtraction to allow for INFINITY in comparison
marginComparison(double lhs,double rhs,double margin)88 bool marginComparison(double lhs, double rhs, double margin) {
89 return (lhs + margin >= rhs) && (rhs + margin >= lhs);
90 }
91
92 template <typename FloatingPoint>
write(std::ostream & out,FloatingPoint num)93 void write(std::ostream& out, FloatingPoint num) {
94 out << std::scientific
95 << std::setprecision(std::numeric_limits<FloatingPoint>::max_digits10 - 1)
96 << num;
97 }
98
99 } // end anonymous namespace
100
101 namespace Matchers {
102 namespace Floating {
103
104 enum class FloatingPointKind : uint8_t {
105 Float,
106 Double
107 };
108
109
WithinAbsMatcher(double target,double margin)110 WithinAbsMatcher::WithinAbsMatcher(double target, double margin)
111 :m_target{ target }, m_margin{ margin } {
112 CATCH_ENFORCE(margin >= 0, "Invalid margin: " << margin << '.'
113 << " Margin has to be non-negative.");
114 }
115
116 // Performs equivalent check of std::fabs(lhs - rhs) <= margin
117 // But without the subtraction to allow for INFINITY in comparison
match(double const & matchee) const118 bool WithinAbsMatcher::match(double const& matchee) const {
119 return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee);
120 }
121
describe() const122 std::string WithinAbsMatcher::describe() const {
123 return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target);
124 }
125
126
WithinUlpsMatcher(double target,uint64_t ulps,FloatingPointKind baseType)127 WithinUlpsMatcher::WithinUlpsMatcher(double target, uint64_t ulps, FloatingPointKind baseType)
128 :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } {
129 CATCH_ENFORCE(m_type == FloatingPointKind::Double
130 || m_ulps < (std::numeric_limits<uint32_t>::max)(),
131 "Provided ULP is impossibly large for a float comparison.");
132 }
133
134 #if defined(__clang__)
135 #pragma clang diagnostic push
136 // Clang <3.5 reports on the default branch in the switch below
137 #pragma clang diagnostic ignored "-Wunreachable-code"
138 #endif
139
match(double const & matchee) const140 bool WithinUlpsMatcher::match(double const& matchee) const {
141 switch (m_type) {
142 case FloatingPointKind::Float:
143 return almostEqualUlps<float>(static_cast<float>(matchee), static_cast<float>(m_target), m_ulps);
144 case FloatingPointKind::Double:
145 return almostEqualUlps<double>(matchee, m_target, m_ulps);
146 default:
147 CATCH_INTERNAL_ERROR( "Unknown FloatingPointKind value" );
148 }
149 }
150
151 #if defined(__clang__)
152 #pragma clang diagnostic pop
153 #endif
154
describe() const155 std::string WithinUlpsMatcher::describe() const {
156 std::stringstream ret;
157
158 ret << "is within " << m_ulps << " ULPs of ";
159
160 if (m_type == FloatingPointKind::Float) {
161 write(ret, static_cast<float>(m_target));
162 ret << 'f';
163 } else {
164 write(ret, m_target);
165 }
166
167 ret << " ([";
168 if (m_type == FloatingPointKind::Double) {
169 write(ret, step(m_target, static_cast<double>(-INFINITY), m_ulps));
170 ret << ", ";
171 write(ret, step(m_target, static_cast<double>( INFINITY), m_ulps));
172 } else {
173 // We have to cast INFINITY to float because of MinGW, see #1782
174 write(ret, step(static_cast<float>(m_target), static_cast<float>(-INFINITY), m_ulps));
175 ret << ", ";
176 write(ret, step(static_cast<float>(m_target), static_cast<float>( INFINITY), m_ulps));
177 }
178 ret << "])";
179
180 return ret.str();
181 }
182
WithinRelMatcher(double target,double epsilon)183 WithinRelMatcher::WithinRelMatcher(double target, double epsilon):
184 m_target(target),
185 m_epsilon(epsilon){
186 CATCH_ENFORCE(m_epsilon >= 0., "Relative comparison with epsilon < 0 does not make sense.");
187 CATCH_ENFORCE(m_epsilon < 1., "Relative comparison with epsilon >= 1 does not make sense.");
188 }
189
match(double const & matchee) const190 bool WithinRelMatcher::match(double const& matchee) const {
191 const auto relMargin = m_epsilon * (std::max)(std::fabs(matchee), std::fabs(m_target));
192 return marginComparison(matchee, m_target,
193 std::isinf(relMargin)? 0 : relMargin);
194 }
195
describe() const196 std::string WithinRelMatcher::describe() const {
197 Catch::ReusableStringStream sstr;
198 sstr << "and " << m_target << " are within " << m_epsilon * 100. << "% of each other";
199 return sstr.str();
200 }
201
202 }// namespace Floating
203
204
205
WithinULP(double target,uint64_t maxUlpDiff)206 Floating::WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff) {
207 return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Double);
208 }
209
WithinULP(float target,uint64_t maxUlpDiff)210 Floating::WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff) {
211 return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Float);
212 }
213
WithinAbs(double target,double margin)214 Floating::WithinAbsMatcher WithinAbs(double target, double margin) {
215 return Floating::WithinAbsMatcher(target, margin);
216 }
217
WithinRel(double target,double eps)218 Floating::WithinRelMatcher WithinRel(double target, double eps) {
219 return Floating::WithinRelMatcher(target, eps);
220 }
221
WithinRel(double target)222 Floating::WithinRelMatcher WithinRel(double target) {
223 return Floating::WithinRelMatcher(target, std::numeric_limits<double>::epsilon() * 100);
224 }
225
WithinRel(float target,float eps)226 Floating::WithinRelMatcher WithinRel(float target, float eps) {
227 return Floating::WithinRelMatcher(target, eps);
228 }
229
WithinRel(float target)230 Floating::WithinRelMatcher WithinRel(float target) {
231 return Floating::WithinRelMatcher(target, std::numeric_limits<float>::epsilon() * 100);
232 }
233
234
235 } // namespace Matchers
236 } // namespace Catch
237
238