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 } //end anonymous namespace
63
64 #if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER)
65
66 #if defined(__clang__)
67 #pragma clang diagnostic push
68 // The long double overload is currently unused
69 #pragma clang diagnostic ignored "-Wunused-function"
70 #endif
71
nextafter(float x,float y)72 float nextafter(float x, float y) {
73 return ::nextafterf(x, y);
74 }
75
nextafter(double x,double y)76 double nextafter(double x, double y) {
77 return ::nextafter(x, y);
78 }
79
nextafter(long double x,long double y)80 long double nextafter(long double x, long double y) {
81 return ::nextafterl(x, y);
82 }
83
84 #if defined(__clang__)
85 #pragma clang diagnostic pop
86 #endif
87
88 #endif // ^^^ CATCH_CONFIG_GLOBAL_NEXTAFTER ^^^
89
90 namespace {
91
92 template <typename FP>
step(FP start,FP direction,uint64_t steps)93 FP step(FP start, FP direction, uint64_t steps) {
94 for (uint64_t i = 0; i < steps; ++i) {
95 #if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER)
96 start = Catch::nextafter(start, direction);
97 #else
98 start = std::nextafter(start, direction);
99 #endif
100 }
101 return start;
102 }
103
104 // Performs equivalent check of std::fabs(lhs - rhs) <= margin
105 // But without the subtraction to allow for INFINITY in comparison
marginComparison(double lhs,double rhs,double margin)106 bool marginComparison(double lhs, double rhs, double margin) {
107 return (lhs + margin >= rhs) && (rhs + margin >= lhs);
108 }
109
110 template <typename FloatingPoint>
write(std::ostream & out,FloatingPoint num)111 void write(std::ostream& out, FloatingPoint num) {
112 out << std::scientific
113 << std::setprecision(std::numeric_limits<FloatingPoint>::max_digits10 - 1)
114 << num;
115 }
116
117 } // end anonymous namespace
118
119 namespace Matchers {
120 namespace Floating {
121
122 enum class FloatingPointKind : uint8_t {
123 Float,
124 Double
125 };
126
127
WithinAbsMatcher(double target,double margin)128 WithinAbsMatcher::WithinAbsMatcher(double target, double margin)
129 :m_target{ target }, m_margin{ margin } {
130 CATCH_ENFORCE(margin >= 0, "Invalid margin: " << margin << '.'
131 << " Margin has to be non-negative.");
132 }
133
134 // Performs equivalent check of std::fabs(lhs - rhs) <= margin
135 // But without the subtraction to allow for INFINITY in comparison
match(double const & matchee) const136 bool WithinAbsMatcher::match(double const& matchee) const {
137 return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee);
138 }
139
describe() const140 std::string WithinAbsMatcher::describe() const {
141 return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target);
142 }
143
144
WithinUlpsMatcher(double target,uint64_t ulps,FloatingPointKind baseType)145 WithinUlpsMatcher::WithinUlpsMatcher(double target, uint64_t ulps, FloatingPointKind baseType)
146 :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } {
147 CATCH_ENFORCE(m_type == FloatingPointKind::Double
148 || m_ulps < (std::numeric_limits<uint32_t>::max)(),
149 "Provided ULP is impossibly large for a float comparison.");
150 }
151
152 #if defined(__clang__)
153 #pragma clang diagnostic push
154 // Clang <3.5 reports on the default branch in the switch below
155 #pragma clang diagnostic ignored "-Wunreachable-code"
156 #endif
157
match(double const & matchee) const158 bool WithinUlpsMatcher::match(double const& matchee) const {
159 switch (m_type) {
160 case FloatingPointKind::Float:
161 return almostEqualUlps<float>(static_cast<float>(matchee), static_cast<float>(m_target), m_ulps);
162 case FloatingPointKind::Double:
163 return almostEqualUlps<double>(matchee, m_target, m_ulps);
164 default:
165 CATCH_INTERNAL_ERROR( "Unknown FloatingPointKind value" );
166 }
167 }
168
169 #if defined(__clang__)
170 #pragma clang diagnostic pop
171 #endif
172
describe() const173 std::string WithinUlpsMatcher::describe() const {
174 std::stringstream ret;
175
176 ret << "is within " << m_ulps << " ULPs of ";
177
178 if (m_type == FloatingPointKind::Float) {
179 write(ret, static_cast<float>(m_target));
180 ret << 'f';
181 } else {
182 write(ret, m_target);
183 }
184
185 ret << " ([";
186 if (m_type == FloatingPointKind::Double) {
187 write(ret, step(m_target, static_cast<double>(-INFINITY), m_ulps));
188 ret << ", ";
189 write(ret, step(m_target, static_cast<double>( INFINITY), m_ulps));
190 } else {
191 // We have to cast INFINITY to float because of MinGW, see #1782
192 write(ret, step(static_cast<float>(m_target), static_cast<float>(-INFINITY), m_ulps));
193 ret << ", ";
194 write(ret, step(static_cast<float>(m_target), static_cast<float>( INFINITY), m_ulps));
195 }
196 ret << "])";
197
198 return ret.str();
199 }
200
WithinRelMatcher(double target,double epsilon)201 WithinRelMatcher::WithinRelMatcher(double target, double epsilon):
202 m_target(target),
203 m_epsilon(epsilon){
204 CATCH_ENFORCE(m_epsilon >= 0., "Relative comparison with epsilon < 0 does not make sense.");
205 CATCH_ENFORCE(m_epsilon < 1., "Relative comparison with epsilon >= 1 does not make sense.");
206 }
207
match(double const & matchee) const208 bool WithinRelMatcher::match(double const& matchee) const {
209 const auto relMargin = m_epsilon * (std::max)(std::fabs(matchee), std::fabs(m_target));
210 return marginComparison(matchee, m_target,
211 std::isinf(relMargin)? 0 : relMargin);
212 }
213
describe() const214 std::string WithinRelMatcher::describe() const {
215 Catch::ReusableStringStream sstr;
216 sstr << "and " << m_target << " are within " << m_epsilon * 100. << "% of each other";
217 return sstr.str();
218 }
219
220 }// namespace Floating
221
222
223
WithinULP(double target,uint64_t maxUlpDiff)224 Floating::WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff) {
225 return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Double);
226 }
227
WithinULP(float target,uint64_t maxUlpDiff)228 Floating::WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff) {
229 return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Float);
230 }
231
WithinAbs(double target,double margin)232 Floating::WithinAbsMatcher WithinAbs(double target, double margin) {
233 return Floating::WithinAbsMatcher(target, margin);
234 }
235
WithinRel(double target,double eps)236 Floating::WithinRelMatcher WithinRel(double target, double eps) {
237 return Floating::WithinRelMatcher(target, eps);
238 }
239
WithinRel(double target)240 Floating::WithinRelMatcher WithinRel(double target) {
241 return Floating::WithinRelMatcher(target, std::numeric_limits<double>::epsilon() * 100);
242 }
243
WithinRel(float target,float eps)244 Floating::WithinRelMatcher WithinRel(float target, float eps) {
245 return Floating::WithinRelMatcher(target, eps);
246 }
247
WithinRel(float target)248 Floating::WithinRelMatcher WithinRel(float target) {
249 return Floating::WithinRelMatcher(target, std::numeric_limits<float>::epsilon() * 100);
250 }
251
252
253 } // namespace Matchers
254 } // namespace Catch
255
256