]>
Commit | Line | Data |
---|---|---|
92f5a8d4 TL |
1 | // Copyright Nick Thompson, 2019 |
2 | // Use, modification and distribution are subject to the | |
3 | // Boost Software License, Version 1.0. | |
4 | // (See accompanying file LICENSE_1_0.txt | |
5 | // or copy at http://www.boost.org/LICENSE_1_0.txt) | |
6 | ||
7 | #ifndef BOOST_MATH_TEST_TEST_HPP | |
8 | #define BOOST_MATH_TEST_TEST_HPP | |
9 | #include <atomic> | |
10 | #include <iostream> | |
11 | #include <iomanip> | |
12 | #include <cmath> // for std::isnan | |
13 | #include <boost/assert.hpp> | |
14 | #include <boost/math/special_functions/next.hpp> | |
20effc67 | 15 | #include <boost/math/special_functions/trunc.hpp> |
92f5a8d4 TL |
16 | #include <boost/core/demangle.hpp> |
17 | ||
18 | namespace boost { namespace math { namespace test { | |
19 | ||
20 | namespace detail { | |
21 | static std::atomic<int64_t> global_error_count{0}; | |
22 | static std::atomic<int64_t> total_ulp_distance{0}; | |
23 | } | |
24 | ||
25 | template<class Real> | |
26 | bool check_mollified_close(Real expected, Real computed, Real tol, std::string const & filename, std::string const & function, int line) | |
27 | { | |
28 | using std::isnan; | |
29 | BOOST_ASSERT_MSG(!isnan(tol), "Tolerance cannot be a nan."); | |
30 | BOOST_ASSERT_MSG(!isnan(expected), "Expected value cannot be a nan."); | |
31 | BOOST_ASSERT_MSG(tol >= 0, "Tolerance must be non-negative."); | |
32 | if (isnan(computed)) { | |
33 | std::ios_base::fmtflags f( std::cerr.flags() ); | |
34 | std::cerr << std::setprecision(3); | |
35 | std::cerr << "\033[0;31mError at " << filename << ":" << function << ":" << line << ":\n" | |
36 | << " \033[0m Computed value is a nan\n"; | |
37 | std::cerr.flags(f); | |
38 | ++detail::global_error_count; | |
39 | return false; | |
40 | } | |
41 | using std::max; | |
42 | using std::abs; | |
43 | Real denom = (max)(abs(expected), Real(1)); | |
44 | Real mollified_relative_error = abs(expected - computed)/denom; | |
45 | if (mollified_relative_error > tol) | |
46 | { | |
47 | Real dist = abs(boost::math::float_distance(expected, computed)); | |
48 | detail::total_ulp_distance += static_cast<int64_t>(dist); | |
49 | std::ios_base::fmtflags f( std::cerr.flags() ); | |
50 | std::cerr << std::setprecision(3); | |
51 | std::cerr << "\033[0;31mError at " << filename << ":" << function << ":" << line << ":\n" | |
52 | << " \033[0m Mollified relative error in " << boost::core::demangle(typeid(Real).name())<< " precision is " << mollified_relative_error | |
53 | << ", which exceeds " << tol << ", error/tol = " << mollified_relative_error/tol << ".\n" | |
20effc67 | 54 | << std::setprecision(std::numeric_limits<Real>::max_digits10) << std::showpos |
92f5a8d4 TL |
55 | << " Expected: " << std::defaultfloat << std::fixed << expected << std::hexfloat << " = " << expected << "\n" |
56 | << " Computed: " << std::defaultfloat << std::fixed << computed << std::hexfloat << " = " << computed << "\n" | |
57 | << std::defaultfloat | |
58 | << " ULP distance: " << dist << "\n"; | |
59 | std::cerr.flags(f); | |
60 | ++detail::global_error_count; | |
61 | ||
62 | return false; | |
63 | } | |
64 | return true; | |
65 | } | |
66 | ||
67 | template<class PreciseReal, class Real> | |
68 | bool check_ulp_close(PreciseReal expected1, Real computed, size_t ulps, std::string const & filename, std::string const & function, int line) | |
69 | { | |
70 | using std::max; | |
71 | using std::abs; | |
72 | using std::isnan; | |
20effc67 | 73 | using boost::math::lltrunc; |
92f5a8d4 TL |
74 | // Of course integers can be expected values, and they are exact: |
75 | if (!std::is_integral<PreciseReal>::value) { | |
92f5a8d4 | 76 | BOOST_ASSERT_MSG(!isnan(expected1), "Expected value cannot be a nan."); |
20effc67 TL |
77 | if (sizeof(PreciseReal) < sizeof(Real)) { |
78 | std::ostringstream err; | |
79 | err << "\n\tThe expected number must be computed in higher (or equal) precision than the number being tested.\n"; | |
80 | err << "\tType of expected is " << boost::core::demangle(typeid(PreciseReal).name()) << ", which occupies " << sizeof(PreciseReal) << " bytes.\n"; | |
81 | err << "\tType of computed is " << boost::core::demangle(typeid(Real).name()) << ", which occupies " << sizeof(Real) << " bytes.\n"; | |
82 | throw std::logic_error(err.str()); | |
83 | } | |
92f5a8d4 TL |
84 | } |
85 | ||
86 | if (isnan(computed)) | |
87 | { | |
88 | std::ios_base::fmtflags f( std::cerr.flags() ); | |
89 | std::cerr << std::setprecision(3); | |
90 | std::cerr << "\033[0;31mError at " << filename << ":" << function << ":" << line << ":\n" | |
91 | << " \033[0m Computed value is a nan\n"; | |
92 | std::cerr.flags(f); | |
93 | ++detail::global_error_count; | |
94 | return false; | |
95 | } | |
96 | ||
97 | Real expected = Real(expected1); | |
98 | Real dist = abs(boost::math::float_distance(expected, computed)); | |
99 | if (dist > ulps) | |
100 | { | |
20effc67 TL |
101 | detail::total_ulp_distance += static_cast<int64_t>(lltrunc(dist)); |
102 | Real abs_expected = abs(expected); | |
103 | Real denom = (max)(abs_expected, Real(1)); | |
92f5a8d4 TL |
104 | Real mollified_relative_error = abs(expected - computed)/denom; |
105 | std::ios_base::fmtflags f( std::cerr.flags() ); | |
106 | std::cerr << std::setprecision(3); | |
107 | std::cerr << "\033[0;31mError at " << filename << ":" << function << ":" << line << ":\n" | |
108 | << " \033[0m ULP distance in " << boost::core::demangle(typeid(Real).name())<< " precision is " << dist | |
109 | << ", which exceeds " << ulps; | |
110 | if (ulps > 0) | |
111 | { | |
112 | std::cerr << ", error/ulps = " << dist/static_cast<Real>(ulps) << ".\n"; | |
113 | } | |
114 | else | |
115 | { | |
116 | std::cerr << ".\n"; | |
117 | } | |
20effc67 | 118 | std::cerr << std::setprecision(std::numeric_limits<Real>::max_digits10) << std::showpos |
92f5a8d4 TL |
119 | << " Expected: " << std::defaultfloat << std::fixed << expected << std::hexfloat << " = " << expected << "\n" |
120 | << " Computed: " << std::defaultfloat << std::fixed << computed << std::hexfloat << " = " << computed << "\n" | |
121 | << std::defaultfloat | |
122 | << " Mollified relative error: " << mollified_relative_error << "\n"; | |
123 | std::cerr.flags(f); | |
124 | ++detail::global_error_count; | |
125 | return false; | |
126 | } | |
127 | return true; | |
128 | } | |
129 | ||
20effc67 TL |
130 | template<typename Real> |
131 | bool check_le(Real lesser, Real greater, std::string const & filename, std::string const & function, int line) | |
132 | { | |
133 | using std::max; | |
134 | using std::abs; | |
135 | using std::isnan; | |
136 | ||
137 | if (isnan(lesser)) | |
138 | { | |
139 | std::ios_base::fmtflags f( std::cerr.flags() ); | |
140 | std::cerr << std::setprecision(3); | |
141 | std::cerr << "\033[0;31mError at " << filename << ":" << function << ":" << line << ":\n" | |
142 | << " \033[0m Lesser value is a nan\n"; | |
143 | std::cerr.flags(f); | |
144 | ++detail::global_error_count; | |
145 | return false; | |
146 | } | |
147 | ||
148 | if (isnan(greater)) | |
149 | { | |
150 | std::ios_base::fmtflags f( std::cerr.flags() ); | |
151 | std::cerr << std::setprecision(3); | |
152 | std::cerr << "\033[0;31mError at " << filename << ":" << function << ":" << line << ":\n" | |
153 | << " \033[0m Greater value is a nan\n"; | |
154 | std::cerr.flags(f); | |
155 | ++detail::global_error_count; | |
156 | return false; | |
157 | } | |
158 | ||
159 | if (lesser > greater) | |
160 | { | |
161 | std::ios_base::fmtflags f( std::cerr.flags() ); | |
162 | std::cerr << std::setprecision(3); | |
163 | std::cerr << "\033[0;31mError at " << filename << ":" << function << ":" << line << ":\n" | |
164 | << " \033[0m Condition " << lesser << " \u2264 " << greater << " is violated in " << boost::core::demangle(typeid(Real).name()) << " precision.\n"; | |
165 | std::cerr << std::setprecision(std::numeric_limits<Real>::max_digits10) << std::showpos | |
166 | << " \"Lesser\" : " << std::defaultfloat << std::fixed << lesser << " = " << std::scientific << lesser << std::hexfloat << " = " << lesser << "\n" | |
167 | << " \"Greater\": " << std::defaultfloat << std::fixed << greater << " = " << std::scientific << greater << std::hexfloat << " = " << greater << "\n" | |
168 | << std::defaultfloat; | |
169 | std::cerr.flags(f); | |
170 | ++detail::global_error_count; | |
171 | return false; | |
172 | } | |
173 | return true; | |
174 | } | |
175 | ||
176 | ||
177 | template<class PreciseReal, class Real> | |
178 | bool check_conditioned_error(Real abscissa, PreciseReal expected1, PreciseReal expected_derivative, Real computed, Real acceptable_badness, std::string const & filename, std::string const & function, int line) | |
179 | { | |
180 | using std::max; | |
181 | using std::abs; | |
182 | using std::isnan; | |
183 | // Of course integers can be expected values, and they are exact: | |
184 | if (!std::is_integral<PreciseReal>::value) { | |
185 | BOOST_ASSERT_MSG(sizeof(PreciseReal) >= sizeof(Real), | |
186 | "The expected number must be computed in higher (or equal) precision than the number being tested."); | |
187 | BOOST_ASSERT_MSG(!isnan(abscissa), "Expected abscissa cannot be a nan."); | |
188 | BOOST_ASSERT_MSG(!isnan(expected1), "Expected value cannot be a nan."); | |
189 | BOOST_ASSERT_MSG(!isnan(expected_derivative), "Expected derivative cannot be a nan."); | |
190 | } | |
191 | BOOST_ASSERT_MSG(acceptable_badness >= 1, "Acceptable badness scale must be >= 1, and in general should = 1 exactly."); | |
192 | ||
193 | if (isnan(computed)) | |
194 | { | |
195 | std::ios_base::fmtflags f(std::cerr.flags()); | |
196 | std::cerr << std::setprecision(3); | |
197 | std::cerr << "\033[0;31mError at " << filename << ":" << function << ":" << line << ":\n" | |
198 | << " \033[0m Computed value is a nan\n"; | |
199 | std::cerr.flags(f); | |
200 | ++detail::global_error_count; | |
201 | return false; | |
202 | } | |
203 | ||
204 | Real mu = std::numeric_limits<Real>::epsilon()/2; | |
205 | Real expected = Real(expected1); | |
206 | // Relative error is undefined. Therefore we must use |f(x(1+eps))| le mu|xf'(x)|. | |
207 | if (expected == 0) | |
208 | { | |
209 | Real tol = acceptable_badness*mu*abs(abscissa*expected_derivative); | |
210 | if (abs(computed) > tol) | |
211 | { | |
212 | std::ios_base::fmtflags f( std::cerr.flags() ); | |
213 | std::cerr << std::setprecision(3); | |
214 | std::cerr << "\033[0;31mError at " << filename << ":" << function << ":" << line << ":\n"; | |
215 | std::cerr << std::setprecision(std::numeric_limits<Real>::max_digits10) << std::showpos; | |
216 | std::cerr << "\033[0m Error at abscissa " << std::defaultfloat << std::fixed << abscissa << " = " << std::hexfloat << abscissa << "\n"; | |
217 | std::cerr << " Given that the expected value is zero, the computed value in " << boost::core::demangle(typeid(Real).name()) << " precision must satisfy |f(x)| <= " << tol << ".\n"; | |
218 | std::cerr << " But the computed value is " << std::defaultfloat << std::fixed << computed << std::hexfloat << " = " << computed << "\n"; | |
219 | std::cerr.flags(f); | |
220 | ++detail::global_error_count; | |
221 | return false; | |
222 | } | |
223 | } | |
224 | // 1 ULP accuracy * acceptable_badness is always acceptable, independent of condition number: | |
225 | if (abs(boost::math::float_distance(Real(expected), computed)) <= acceptable_badness) | |
226 | { | |
227 | return true; | |
228 | } | |
229 | Real expected_prime = Real(expected_derivative); | |
230 | PreciseReal precise_abscissa = abscissa; | |
231 | PreciseReal cond = abs(precise_abscissa*expected_prime/expected); | |
232 | PreciseReal relative_error = abs((expected - PreciseReal(computed))/expected); | |
233 | // If the condition number is small, then we revert to allowing 1ULP accuracy, i.e., one incorrect bit. | |
234 | Real tol = cond*mu; | |
235 | tol *= acceptable_badness; | |
236 | if (relative_error > tol) | |
237 | { | |
238 | std::ios_base::fmtflags f( std::cerr.flags() ); | |
239 | std::cerr << std::setprecision(3); | |
240 | std::cerr << "\033[0;31mError at " << filename << ":" << function << ":" << line << "\n"; | |
241 | std::cerr << std::setprecision(std::numeric_limits<Real>::max_digits10); | |
242 | std::cerr << "\033[0m The relative error at abscissa x = " << std::defaultfloat << std::fixed << abscissa << " = " << std::hexfloat << abscissa | |
243 | << " in " << boost::core::demangle(typeid(Real).name()) << " precision is " << std::scientific << relative_error << "\n" | |
244 | << " This exceeds the tolerance " << tol << "\n" | |
245 | << std::showpos | |
246 | << " Expected: " << std::defaultfloat << std::fixed << expected << " = " << std::scientific << expected << std::hexfloat << " = " << expected << "\n" | |
247 | << " Computed: " << std::defaultfloat << std::fixed << computed << " = " << std::scientific << computed << std::hexfloat << " = " << computed << "\n" | |
248 | << " Condition number of function evaluation: " << std::noshowpos << std::defaultfloat << std::scientific << cond << " = " << std::fixed << cond << "\n" | |
249 | << " Badness scale required to make this message go away: " << std::defaultfloat << relative_error/(cond*mu) << "\n"; | |
250 | std::cerr.flags(f); | |
251 | ++detail::global_error_count; | |
252 | return false; | |
253 | } | |
254 | return true; | |
255 | } | |
256 | ||
257 | ||
258 | template<class PreciseReal, class Real> | |
259 | bool check_absolute_error(PreciseReal expected1, Real computed, Real acceptable_error, std::string const & filename, std::string const & function, int line) | |
260 | { | |
261 | using std::max; | |
262 | using std::abs; | |
263 | using std::isnan; | |
264 | // Of course integers can be expected values, and they are exact: | |
265 | if (!std::is_integral<PreciseReal>::value) { | |
266 | BOOST_ASSERT_MSG(sizeof(PreciseReal) >= sizeof(Real), | |
267 | "The expected number must be computed in higher (or equal) precision than the number being tested."); | |
268 | BOOST_ASSERT_MSG(!isnan(expected1), "Expected value cannot be a nan (use CHECK_NAN if this is your intention)."); | |
269 | } | |
270 | BOOST_ASSERT_MSG(acceptable_error > 0, "Error must be > 0."); | |
271 | ||
272 | if (isnan(computed)) | |
273 | { | |
274 | std::ios_base::fmtflags f(std::cerr.flags()); | |
275 | std::cerr << std::setprecision(3); | |
276 | std::cerr << "\033[0;31mError at " << filename << ":" << function << ":" << line << ":\n" | |
277 | << " \033[0m Computed value is a nan\n"; | |
278 | std::cerr.flags(f); | |
279 | ++detail::global_error_count; | |
280 | return false; | |
281 | } | |
282 | ||
283 | Real expected = Real(expected1); | |
284 | Real error = abs(expected - computed); | |
285 | if (error > acceptable_error) | |
286 | { | |
287 | std::ios_base::fmtflags f( std::cerr.flags() ); | |
288 | std::cerr << std::setprecision(3); | |
289 | std::cerr << "\033[0;31mError at " << filename << ":" << function << ":" << line << "\n"; | |
290 | std::cerr << std::setprecision(std::numeric_limits<Real>::max_digits10); | |
291 | std::cerr << "\033[0m The absolute error in " << boost::core::demangle(typeid(Real).name()) << " precision is " << std::scientific << error << "\n" | |
292 | << " This exceeds the acceptable error " << acceptable_error << "\n" | |
293 | << std::showpos | |
294 | << " Expected: " << std::defaultfloat << std::fixed << expected << " = " << std::scientific << expected << std::hexfloat << " = " << expected << "\n" | |
295 | << " Computed: " << std::defaultfloat << std::fixed << computed << " = " << std::scientific << computed<< std::hexfloat << " = " << computed << "\n" | |
296 | << " Error/Acceptable error: " << std::defaultfloat << error/acceptable_error << "\n"; | |
297 | std::cerr.flags(f); | |
298 | ++detail::global_error_count; | |
299 | return false; | |
300 | } | |
301 | return true; | |
302 | } | |
303 | ||
304 | template<class Real> | |
305 | bool check_nan(Real x, std::string const & filename, std::string const & function, int line) | |
306 | { | |
307 | using std::isnan; | |
308 | if (!isnan(x)) { | |
309 | std::ios_base::fmtflags f( std::cerr.flags() ); | |
310 | std::cerr << "\033[0;31mError at " << filename << ":" << function << ":" << line << "\n"; | |
311 | std::cerr << "\033[0m The computed value should be a nan, but is instead " << std::defaultfloat << std::fixed << x << " = " << std::scientific << x << std::hexfloat << " = " << x << "\n"; | |
312 | std::cerr.flags(f); | |
313 | ++detail::global_error_count; | |
314 | return false; | |
315 | } | |
316 | return true; | |
317 | } | |
318 | ||
319 | template<class Real> | |
320 | bool check_equal(Real x, Real y, std::string const & filename, std::string const & function, int line) | |
321 | { | |
322 | using std::isnan; | |
323 | if (x != y) { | |
324 | std::ios_base::fmtflags f( std::cerr.flags() ); | |
325 | std::cerr << "\033[0;31mError at " << filename << ":" << function << ":" << line << "\n"; | |
326 | std::cerr << "\033[0m Condition '" << x << " == " << y << "' is not satisfied:\n"; | |
327 | if (std::is_floating_point<Real>::value) { | |
328 | std::cerr << " Expected = " << std::defaultfloat << std::fixed << x << " = " << std::scientific << x << std::hexfloat << " = " << x << "\n"; | |
329 | std::cerr << " Computed = " << std::defaultfloat << std::fixed << y << " = " << std::scientific << y << std::hexfloat << " = " << y << "\n"; | |
330 | } else { | |
331 | std::cerr << " Expected: " << x << " = " << "0x" << std::hex << x << "\n"; | |
332 | std::cerr << std::dec; | |
333 | std::cerr << " Computed: " << y << " = " << "0x" << std::hex << y << "\n"; | |
334 | } | |
335 | std::cerr.flags(f); | |
336 | ++detail::global_error_count; | |
337 | return false; | |
338 | } | |
339 | return true; | |
340 | } | |
341 | ||
92f5a8d4 TL |
342 | |
343 | int report_errors() | |
344 | { | |
345 | if (detail::global_error_count > 0) | |
346 | { | |
347 | std::cerr << "\033[0;31mError count: " << detail::global_error_count; | |
348 | if (detail::total_ulp_distance > 0) { | |
349 | std::cerr << ", total ulp distance = " << detail::total_ulp_distance << "\n\033[0m"; | |
350 | } | |
351 | else { | |
352 | // else we overflowed the ULPs counter and all we could print is a bizarre negative number. | |
353 | std::cerr << "\n\033[0m"; | |
354 | } | |
355 | ||
356 | detail::global_error_count = 0; | |
357 | detail::total_ulp_distance = 0; | |
358 | return 1; | |
359 | } | |
360 | std::cout << "\x1B[32mNo errors detected.\n\033[0m"; | |
361 | return 0; | |
362 | } | |
363 | ||
364 | }}} | |
365 | ||
366 | #define CHECK_MOLLIFIED_CLOSE(X, Y, Z) boost::math::test::check_mollified_close< typename std::remove_reference<decltype((Y))>::type>((X), (Y), (Z), __FILE__, __func__, __LINE__) | |
367 | ||
368 | #define CHECK_ULP_CLOSE(X, Y, Z) boost::math::test::check_ulp_close((X), (Y), (Z), __FILE__, __func__, __LINE__) | |
369 | ||
20effc67 TL |
370 | #define CHECK_LE(X, Y) boost::math::test::check_le((X), (Y), __FILE__, __func__, __LINE__) |
371 | ||
372 | #define CHECK_NAN(X) boost::math::test::check_nan((X), __FILE__, __func__, __LINE__) | |
373 | ||
374 | #define CHECK_EQUAL(X, Y) boost::math::test::check_equal((X), (Y), __FILE__, __func__, __LINE__) | |
375 | ||
376 | #define CHECK_CONDITIONED_ERROR(V, W, X, Y, Z) boost::math::test::check_conditioned_error((V), (W), (X), (Y), (Z), __FILE__, __func__, __LINE__) | |
377 | ||
378 | #define CHECK_ABSOLUTE_ERROR(X, Y, Z) boost::math::test::check_absolute_error((X), (Y), (Z), __FILE__, __func__, __LINE__) | |
379 | ||
92f5a8d4 | 380 | #endif |