• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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