| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | //====================================================================================================================== | ||
| 2 | /// @file | ||
| 3 | /* | ||
| 4 | TTS - Tiny Test System | ||
| 5 | Copyright : TTS Contributors & Maintainers | ||
| 6 | SPDX-License-Identifier: BSL-1.0 | ||
| 7 | */ | ||
| 8 | //====================================================================================================================== | ||
| 9 | #pragma once | ||
| 10 | |||
| 11 | namespace tts | ||
| 12 | { | ||
| 13 | template<typename Base> struct adapter | ||
| 14 | { | ||
| 15 | 20480 | template<typename U, typename Func> static void run(Base const*& src, U*& dst, Func f) noexcept | |
| 16 | { | ||
| 17 | 20480 | *dst++ = f(*src++); | |
| 18 | 20480 | } | |
| 19 | 9 | static auto retrieve(Base const* src) noexcept | |
| 20 | { | ||
| 21 | 9 | return *src; | |
| 22 | } | ||
| 23 | 9 | static void display(Base const& v) noexcept | |
| 24 | { | ||
| 25 | 9 | output().write(as_text(v)); | |
| 26 | 9 | } | |
| 27 | }; | ||
| 28 | |||
| 29 | namespace _ | ||
| 30 | { | ||
| 31 | template<typename Input, typename Output> struct histogram_entry | ||
| 32 | { | ||
| 33 | bool status; | ||
| 34 | Input input_value; | ||
| 35 | Output found_value, ref_value; | ||
| 36 | }; | ||
| 37 | |||
| 38 | template<typename Type, typename In, typename Out, typename Func> | ||
| 39 | 14 | void compute(In const& inputs, Out& outputs, Func fn) | |
| 40 | { | ||
| 41 | 14 | auto in = inputs.data(); | |
| 42 | 14 | auto end = inputs.data() + inputs.size(); | |
| 43 | 14 | auto out = outputs.data(); | |
| 44 |
2/2✓ Branch 0 taken 23552 times.
✓ Branch 1 taken 8 times.
|
38926 | while(in != end) |
| 45 | 38912 | adapter<Type>::run(in, out, fn); | |
| 46 | 14 | } | |
| 47 | |||
| 48 | 16384 | inline std::size_t last_bucket_less(std::size_t nb_buckets, double ulp) noexcept | |
| 49 | { | ||
| 50 | 4844 | auto next2 = [](double x) | |
| 51 | { | ||
| 52 | 4844 | auto v = static_cast<std::size_t>(_::ceil(x)); | |
| 53 | 4844 | v--; | |
| 54 | 4844 | v |= v >> 1; | |
| 55 | 4844 | v |= v >> 2; | |
| 56 | 4844 | v |= v >> 4; | |
| 57 | 4844 | v |= v >> 8; | |
| 58 | 4844 | v |= v >> 16; | |
| 59 | 4844 | v++; | |
| 60 | 4844 | return v; | |
| 61 | }; | ||
| 62 | |||
| 63 | std::size_t bucket; | ||
| 64 |
2/2✓ Branch 0 taken 11540 times.
✓ Branch 1 taken 4844 times.
|
16384 | if(ulp <= 1.5) bucket = static_cast<std::size_t>(_::ceil(ulp * 2)); |
| 65 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 4844 times.
|
4844 | else if(_::is_inf(ulp)) bucket = nb_buckets - 1; |
| 66 | 4844 | else bucket = _::min(nb_buckets - 2, static_cast<std::size_t>(_::log2(next2(ulp)) + 4)); | |
| 67 | 16384 | return bucket; | |
| 68 | } | ||
| 69 | |||
| 70 | 4 | template<typename... S> void header(S const&... s) | |
| 71 | { | ||
| 72 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
|
4 | if(::tts::is_quiet()) return; |
| 73 |
4/4✓ Branch 2 taken 4 times.
✓ Branch 6 taken 4 times.
✓ Branch 10 taken 4 times.
✓ Branch 14 taken 4 times.
|
4 | ((::tts::output().write("%-*s", 16, s)), ...); |
| 74 |
1/1✓ Branch 3 taken 4 times.
|
4 | ::tts::output().writeln(); |
| 75 | } | ||
| 76 | |||
| 77 | template<typename U, typename R, typename V> | ||
| 78 | 171 | void results(U ulp, unsigned int count, R ratio, auto desc, V const& v) | |
| 79 | { | ||
| 80 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 90 times.
|
171 | assert(desc && "Description cannot be null"); |
| 81 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 90 times.
|
171 | if(::tts::is_quiet()) return; |
| 82 |
2/2✓ Branch 0 taken 30 times.
✓ Branch 1 taken 60 times.
|
171 | if(ulp != -1) ::tts::output().write("%-16.1f%-16u%-16g%s", ulp, count, ratio, desc); |
| 83 |
1/1✓ Branch 2 taken 60 times.
|
114 | else ::tts::output().write("%*s", static_cast<int>(48 + strlen(desc)), desc); // NOSONAR |
| 84 | 171 | adapter<V>::display(v); | |
| 85 |
1/1✓ Branch 3 taken 90 times.
|
171 | ::tts::output().writeln(); |
| 86 | } | ||
| 87 | |||
| 88 | 7 | template<typename P> void print_producer(P const& prod, auto alt) | |
| 89 | { | ||
| 90 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
|
7 | if(::tts::is_quiet()) return; |
| 91 |
2/2✓ Branch 2 taken 1 times.
✓ Branch 5 taken 1 times.
|
2 | if constexpr(::tts::_::described<P>) ::tts::output().writeln(::tts::as_text(prod)); |
| 92 | 5 | else ::tts::output().writeln(alt); | |
| 93 | } | ||
| 94 | } | ||
| 95 | |||
| 96 | template<typename RefType, typename NewType, typename Generator, typename RefFun, typename NewFun> | ||
| 97 | 7 | double ulp_histogram(Generator g, RefFun reference, NewFun challenger) | |
| 98 | { | ||
| 99 | using out_type = std::decay_t<std::invoke_result_t<RefFun, RefType>>; | ||
| 100 | using nout_type = std::decay_t<std::invoke_result_t<NewFun, NewType>>; | ||
| 101 | |||
| 102 | //-- Find how many elements in a block | ||
| 103 |
1/1✓ Branch 2 taken 4 times.
|
7 | std::size_t count = ::tts::arguments().value(std::size_t {4096}, "--block"); |
| 104 | |||
| 105 | //-- Prepare blocks | ||
| 106 |
2/2✓ Branch 1 taken 4 times.
✓ Branch 4 taken 4 times.
|
7 | buffer<out_type> ref_out(count), new_out(count); |
| 107 |
1/1✓ Branch 1 taken 4 times.
|
7 | buffer<RefType> inputs(count); |
| 108 | |||
| 109 |
2/2✓ Branch 1 taken 16384 times.
✓ Branch 2 taken 4 times.
|
28679 | for(std::size_t i = 0; i < inputs.size(); ++i) |
| 110 |
1/1✓ Branch 1 taken 12288 times.
|
28672 | inputs[ i ] = ::tts::produce(type<RefType> {}, g, i, count); |
| 111 | |||
| 112 |
1/1✓ Branch 2 taken 4 times.
|
7 | std::size_t repetition = ::tts::arguments().value(std::size_t {1}, "--loop"); |
| 113 | |||
| 114 | 7 | double max_ulp = 0.; | |
| 115 | 7 | std::size_t nb_buckets = 2 + 1 + 16; | |
| 116 | 7 | std::size_t nb_ulps = 0; | |
| 117 |
1/1✓ Branch 1 taken 4 times.
|
7 | buffer<unsigned int> ulp_map(nb_buckets, 0); |
| 118 | |||
| 119 |
1/1✓ Branch 1 taken 4 times.
|
7 | buffer<_::histogram_entry<NewType, nout_type>> samples(nb_buckets, {false, {}, {}, {}}); |
| 120 | |||
| 121 |
2/2✓ Branch 1 taken 4 times.
✓ Branch 2 taken 4 times.
|
14 | for(std::size_t r = 0; r < repetition; ++r) |
| 122 | { | ||
| 123 | //--- Compute reference & challenger outputs | ||
| 124 |
1/1✓ Branch 1 taken 4 times.
|
7 | _::compute<RefType>(inputs, ref_out, reference); |
| 125 |
1/1✓ Branch 1 taken 4 times.
|
7 | _::compute<NewType>(inputs, new_out, challenger); |
| 126 | |||
| 127 | //--- Compute ulp distances | ||
| 128 |
1/1✓ Branch 1 taken 4 times.
|
7 | buffer<double> ulpdists(count); |
| 129 | |||
| 130 |
2/2✓ Branch 1 taken 16384 times.
✓ Branch 2 taken 4 times.
|
28679 | for(std::size_t i = 0; i < ulpdists.size(); ++i) |
| 131 | { | ||
| 132 | 28672 | nb_ulps++; | |
| 133 |
1/1✓ Branch 3 taken 16384 times.
|
28672 | ulpdists[ i ] = ::tts::ulp_check(ref_out[ i ], new_out[ i ]); |
| 134 | 28672 | max_ulp = _::max(max_ulp, ulpdists[ i ]); | |
| 135 | |||
| 136 | 28672 | auto idx = _::last_bucket_less(nb_buckets, ulpdists[ i ]); | |
| 137 | 28672 | ulp_map[ idx ]++; | |
| 138 | |||
| 139 |
2/2✓ Branch 1 taken 30 times.
✓ Branch 2 taken 16354 times.
|
28672 | if(!samples[ idx ].status) |
| 140 | { | ||
| 141 | 114 | samples[ idx ] = {true, | |
| 142 | 57 | adapter<NewType>::retrieve(&inputs[ i ]), | |
| 143 | 57 | adapter<nout_type>::retrieve(&new_out[ i ]), | |
| 144 | 57 | adapter<nout_type>::retrieve(&ref_out[ i ])}; | |
| 145 | } | ||
| 146 | } | ||
| 147 | } | ||
| 148 | |||
| 149 |
1/1✓ Branch 1 taken 4 times.
|
7 | _::header("Max ULP", "Count (#)", "Ratio Sum (%)", "Samples"); |
| 150 |
1/1✓ Branch 2 taken 4 times.
|
7 | _::separator(!::tts::is_quiet()); |
| 151 | |||
| 152 | 7 | double ratio = 0.; | |
| 153 | |||
| 154 |
2/2✓ Branch 1 taken 76 times.
✓ Branch 2 taken 4 times.
|
140 | for(std::size_t i = 0; i < ulp_map.size(); ++i) |
| 155 | { | ||
| 156 |
2/2✓ Branch 1 taken 30 times.
✓ Branch 2 taken 46 times.
|
133 | if(ulp_map[ i ] != 0) |
| 157 | { | ||
| 158 | 57 | double ulps = 0; | |
| 159 | 57 | ratio += (100. * ulp_map[ i ]) / static_cast<double>(nb_ulps); | |
| 160 | |||
| 161 |
2/2✓ Branch 0 taken 15 times.
✓ Branch 1 taken 15 times.
|
57 | if(i <= 3) ulps = static_cast<double>(i) / 2.0; |
| 162 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
|
30 | else if(i == nb_buckets - 1) ulps = std::numeric_limits<double>::infinity(); |
| 163 | 30 | else ulps = 1 << (i - 4); | |
| 164 | |||
| 165 | 57 | auto [ s, in, out, ref ] = samples[ i ]; | |
| 166 | |||
| 167 |
1/1✓ Branch 2 taken 30 times.
|
57 | _::results(ulps, ulp_map[ i ], ratio, "Input: ", in); |
| 168 |
1/1✓ Branch 1 taken 30 times.
|
57 | _::results(-1., 0, 0., "Found: ", out); |
| 169 |
1/1✓ Branch 1 taken 30 times.
|
57 | _::results(-1., 0, 0., "instead of: ", ref); |
| 170 |
1/1✓ Branch 2 taken 30 times.
|
57 | _::separator(!::tts::is_quiet()); |
| 171 | } | ||
| 172 | } | ||
| 173 | |||
| 174 | 7 | return max_ulp; | |
| 175 | 7 | } | |
| 176 | } | ||
| 177 | |||
| 178 | //====================================================================================================================== | ||
| 179 | /*! | ||
| 180 | @def TTS_ULP_RANGE_CHECK | ||
| 181 | @ingroup test-precision | ||
| 182 | @brief Generate a range based test between two functions | ||
| 183 | |||
| 184 | Evaluates the histogram of ULP difference between two functions run on the same data set and that | ||
| 185 | they lie in a given [ULP distance](rationale.html#ulp). | ||
| 186 | |||
| 187 | @param Producer Data set generator to use. | ||
| 188 | @param RefType Type to use as reference function input. | ||
| 189 | @param NewType Type to use as challenger function input. | ||
| 190 | @param RefFunc Reference function to compare to. | ||
| 191 | @param NewFunc Challenger function to be compared to the reference one. | ||
| 192 | @param Ulpmax Maximal ULPs acceptable for passing the test. | ||
| 193 | |||
| 194 | @groupheader{Example} | ||
| 195 | @snippet doc/ulp_ranges.cpp snippet | ||
| 196 | **/ | ||
| 197 | //====================================================================================================================== | ||
| 198 | #if defined(TTS_DOXYGEN_INVOKED) | ||
| 199 | #define TTS_ULP_RANGE_CHECK(Producer, RefType, NewType, RefFunc, NewFunc, Ulpmax) | ||
| 200 | #else | ||
| 201 | #define TTS_ULP_RANGE_CHECK(Producer, RefType, NewType, RefFunc, NewFunc, Ulpmax) \ | ||
| 202 | [ & ]() \ | ||
| 203 | { \ | ||
| 204 | if(!::tts::is_quiet()) \ | ||
| 205 | ::tts::output().write("Comparing: %s<%s> with %s<%s> using ", \ | ||
| 206 | TTS_STRING(RefFunc), \ | ||
| 207 | TTS_STRING(TTS_REMOVE_PARENS(RefType)), \ | ||
| 208 | TTS_STRING(NewFunc), \ | ||
| 209 | TTS_STRING(TTS_REMOVE_PARENS(NewType))); \ | ||
| 210 | \ | ||
| 211 | auto local_tts_generator = TTS_REMOVE_PARENS(Producer); \ | ||
| 212 | ::tts::_::print_producer(local_tts_generator, TTS_STRING(TTS_REMOVE_PARENS(Producer))); \ | ||
| 213 | \ | ||
| 214 | double tts_ulp_max = Ulpmax; \ | ||
| 215 | double local_tts_threshold = ::tts::arguments().value(tts_ulp_max, "--ulpmax"); \ | ||
| 216 | double local_tts_max_ulp = \ | ||
| 217 | ::tts::ulp_histogram<TTS_REMOVE_PARENS(RefType), TTS_REMOVE_PARENS(NewType)>( \ | ||
| 218 | local_tts_generator, RefFunc, NewFunc); \ | ||
| 219 | \ | ||
| 220 | if(local_tts_max_ulp <= local_tts_threshold) \ | ||
| 221 | { \ | ||
| 222 | ::tts::global_runtime.pass(); \ | ||
| 223 | } \ | ||
| 224 | else \ | ||
| 225 | { \ | ||
| 226 | TTS_FAIL("Expecting: %s similar to %s within %s ULP but found: %s ULP instead", \ | ||
| 227 | TTS_STRING(NewFunc), \ | ||
| 228 | TTS_STRING(RefFunc), \ | ||
| 229 | ::tts::as_text(local_tts_threshold).data(), \ | ||
| 230 | ::tts::as_text(local_tts_max_ulp).data()); \ | ||
| 231 | } \ | ||
| 232 | }() | ||
| 233 | /**/ | ||
| 234 | #endif | ||
| 235 | |||
| 236 | namespace tts | ||
| 237 | { | ||
| 238 | //==================================================================================================================== | ||
| 239 | /*! | ||
| 240 | @brief Data generator using a realistic random distribution | ||
| 241 | |||
| 242 | @groupheader{Example} | ||
| 243 | @code | ||
| 244 | #define TTS_MAIN // No need for main() | ||
| 245 | #include <tts/tts.hpp> | ||
| 246 | |||
| 247 | float ok_x (float x) { return x; } | ||
| 248 | float bad_x(float x) { return x + x*1e-7f; } | ||
| 249 | |||
| 250 | TTS_CASE( "Test range check" ) | ||
| 251 | { | ||
| 252 | TTS_ULP_RANGE_CHECK ( tts::realistic_generator<float>(-100.f,+100.f) | ||
| 253 | , float, float , ok_x, bad_x | ||
| 254 | , 2. | ||
| 255 | ); | ||
| 256 | }; | ||
| 257 | @endcode | ||
| 258 | |||
| 259 | @see realistic_distribution | ||
| 260 | @tparam T Type of data to generate | ||
| 261 | **/ | ||
| 262 | //==================================================================================================================== | ||
| 263 | template<typename T> struct realistic_generator | ||
| 264 | { | ||
| 265 | 1 | realistic_generator(T mn, T mx) | |
| 266 | 1 | : mini(mn) | |
| 267 | 1 | , maxi(mx) | |
| 268 | { | ||
| 269 | 1 | } | |
| 270 | 4096 | T operator()(auto, auto, auto) const | |
| 271 | { | ||
| 272 | 4096 | return ::tts::random_value(mini, maxi); | |
| 273 | } | ||
| 274 | |||
| 275 | private: | ||
| 276 | // The rendering used to be a hidden friend reading these directly. It is a tts::display | ||
| 277 | // specialization now, and needs the same access. | ||
| 278 | template<typename> friend struct display; | ||
| 279 | |||
| 280 | T mini, maxi; | ||
| 281 | }; | ||
| 282 | |||
| 283 | template<typename T> struct display<realistic_generator<T>> | ||
| 284 | { | ||
| 285 | 1 | static text render(realistic_generator<T> const& s) | |
| 286 | { | ||
| 287 | return text {"realistic_generator<%s>(%s,%s)", | ||
| 288 |
1/1✓ Branch 1 taken 1 times.
|
2 | as_text(typename_<T>).data(), |
| 289 |
1/1✓ Branch 1 taken 1 times.
|
2 | as_text(s.mini).data(), |
| 290 |
1/1✓ Branch 3 taken 1 times.
|
3 | as_text(s.maxi).data()}; |
| 291 | } | ||
| 292 | }; | ||
| 293 | } | ||
| 294 |