| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | //====================================================================================================================== | ||
| 2 | /* | ||
| 3 | TTS - Tiny Test System | ||
| 4 | Copyright : TTS Contributors & Maintainers | ||
| 5 | SPDX-License-Identifier: BSL-1.0 | ||
| 6 | */ | ||
| 7 | //====================================================================================================================== | ||
| 8 | #pragma once | ||
| 9 | #include <tts/tools/bitcast.hpp> | ||
| 10 | #include <limits> | ||
| 11 | #include <tts/engine/math.hpp> | ||
| 12 | #include <tts/tools/comparators.hpp> | ||
| 13 | |||
| 14 | namespace tts::_ | ||
| 15 | { | ||
| 16 | //==================================================================================================================== | ||
| 17 | // How TTS measures a distance when nothing more specific is known. Pulled into a base so that a | ||
| 18 | // specialization of tts::precision only has to redefine what it actually changes. | ||
| 19 | //==================================================================================================================== | ||
| 20 | template<typename T> struct builtin_precision | ||
| 21 | { | ||
| 22 | 2213 | static double absolute(T const& a, T const& b) | |
| 23 | { | ||
| 24 | static_assert( | ||
| 25 | !requires { absolute_distance(a, b); }, | ||
| 26 | "[TTS] tts::absolute_distance is no longer a customization point. " | ||
| 27 | "Specialize tts::precision<T>::absolute instead."); | ||
| 28 | |||
| 29 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
8 | if constexpr(std::is_same_v<T, bool>) return a == b ? 0. : 1.; |
| 30 | else if constexpr(std::is_floating_point_v<T>) | ||
| 31 | { | ||
| 32 |
8/8✓ Branch 0 taken 58 times.
✓ Branch 1 taken 591 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 54 times.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 2 times.
✓ Branch 8 taken 593 times.
✓ Branch 9 taken 56 times.
|
1289 | if((a == b) || (is_nan(a) && is_nan(b))) return 0.; |
| 33 |
10/10✓ Branch 1 taken 52 times.
✓ Branch 2 taken 4 times.
✓ Branch 4 taken 48 times.
✓ Branch 5 taken 4 times.
✓ Branch 7 taken 46 times.
✓ Branch 8 taken 2 times.
✓ Branch 10 taken 2 times.
✓ Branch 11 taken 44 times.
✓ Branch 12 taken 12 times.
✓ Branch 13 taken 44 times.
|
109 | if(is_inf(a) || is_inf(b) || is_nan(a) || is_nan(b)) |
| 34 | 24 | return std::numeric_limits<double>::infinity(); | |
| 35 | 85 | return abs(a - b); | |
| 36 | } | ||
| 37 | else if constexpr(std::is_integral_v<T>) | ||
| 38 | 916 | return builtin_precision<double>::absolute(static_cast<double>(a), static_cast<double>(b)); | |
| 39 | else | ||
| 40 | { | ||
| 41 | static_assert( | ||
| 42 | std::is_floating_point_v<T> || std::is_integral_v<T>, | ||
| 43 | "[TTS] TTS_ABSOLUTE_EQUAL requires integral or floating points data to compare. " | ||
| 44 | "Did you mean to use TTS_ALL_ABSOLUTE_EQUAL or to specialize " | ||
| 45 | "tts::precision<T>::absolute ?"); | ||
| 46 | return 0.; | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | 275 | static double relative(T const& a, T const& b) | |
| 51 | { | ||
| 52 | static_assert( | ||
| 53 | !requires { relative_distance(a, b); }, | ||
| 54 | "[TTS] tts::relative_distance is no longer a customization point. " | ||
| 55 | "Specialize tts::precision<T>::relative instead."); | ||
| 56 | |||
| 57 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
8 | if constexpr(std::is_same_v<T, bool>) return a == b ? 0. : 1.; |
| 58 | else if constexpr(std::is_floating_point_v<T>) | ||
| 59 | { | ||
| 60 |
8/8✓ Branch 0 taken 79 times.
✓ Branch 1 taken 37 times.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 73 times.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 3 times.
✓ Branch 8 taken 40 times.
✓ Branch 9 taken 76 times.
|
209 | if((a == b) || (is_nan(a) && is_nan(b))) return 0.; |
| 61 |
10/10✓ Branch 1 taken 72 times.
✓ Branch 2 taken 4 times.
✓ Branch 4 taken 68 times.
✓ Branch 5 taken 4 times.
✓ Branch 7 taken 65 times.
✓ Branch 8 taken 3 times.
✓ Branch 10 taken 3 times.
✓ Branch 11 taken 62 times.
✓ Branch 12 taken 14 times.
✓ Branch 13 taken 62 times.
|
132 | if(is_inf(a) || is_inf(b) || is_nan(a) || is_nan(b)) |
| 62 | 28 | return std::numeric_limits<double>::infinity(); | |
| 63 | 104 | return abs(a - b) / max(T(1), max(abs(a), abs(b))); | |
| 64 | } | ||
| 65 | else if constexpr(std::is_integral_v<T>) | ||
| 66 | 58 | return builtin_precision<double>::relative(static_cast<double>(a), static_cast<double>(b)); | |
| 67 | else | ||
| 68 | { | ||
| 69 | static_assert( | ||
| 70 | std::is_floating_point_v<T> || std::is_integral_v<T>, | ||
| 71 | "[TTS] TTS_RELATIVE_EQUAL requires integral or floating points data to compare. " | ||
| 72 | "Did you mean to use TTS_ALL_RELATIVE_EQUAL or to specialize " | ||
| 73 | "tts::precision<T>::relative ?"); | ||
| 74 | return 0.; | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | 16581 | static double ulp(T const& a, T const& b) | |
| 79 | { | ||
| 80 | static_assert( | ||
| 81 | !requires { ulp_distance(a, b); }, | ||
| 82 | "[TTS] tts::ulp_distance is no longer a customization point. " | ||
| 83 | "Specialize tts::precision<T>::ulp instead."); | ||
| 84 | |||
| 85 | if constexpr(std::is_same_v<T, bool>) | ||
| 86 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
8 | return a == b ? 0. : std::numeric_limits<double>::infinity(); |
| 87 | else if constexpr(std::is_floating_point_v<T>) | ||
| 88 | { | ||
| 89 | using ui_t = std::conditional_t<std::is_same_v<T, float>, std::uint32_t, std::uint64_t>; | ||
| 90 |
8/8✓ Branch 0 taken 4139 times.
✓ Branch 1 taken 68 times.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 4133 times.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 2 times.
✓ Branch 8 taken 72 times.
✓ Branch 9 taken 4135 times.
|
4232 | if((a == b) || (is_nan(a) && is_nan(b))) return 0.; |
| 91 |
2/2✓ Branch 1 taken 5 times.
✓ Branch 2 taken 4130 times.
|
4152 | else if(is_unordered(a, b)) return std::numeric_limits<double>::infinity(); |
| 92 | else | ||
| 93 | { | ||
| 94 | 4145 | auto aa = bitinteger(a); | |
| 95 | 4145 | auto bb = bitinteger(b); | |
| 96 |
2/2✓ Branch 0 taken 11 times.
✓ Branch 1 taken 4119 times.
|
4145 | if(aa > bb) std::swap(aa, bb); |
| 97 | 4145 | auto z = static_cast<ui_t>(bb - aa); | |
| 98 |
2/2✓ Branch 2 taken 4 times.
✓ Branch 3 taken 4126 times.
|
4145 | if(signbit(a) != signbit(b)) ++z; |
| 99 | 4145 | return static_cast<double>(z) / 2.; | |
| 100 | } | ||
| 101 | } | ||
| 102 | else if constexpr(std::is_integral_v<T>) | ||
| 103 | { | ||
| 104 | using u_t = typename std::make_unsigned<T>::type; | ||
| 105 | 12341 | auto ua = static_cast<u_t>(a); | |
| 106 | 12341 | auto ub = static_cast<u_t>(b); | |
| 107 |
2/2✓ Branch 0 taken 5060 times.
✓ Branch 1 taken 7256 times.
|
12341 | return static_cast<double>((a < b) ? (ub - ua) : (ua - ub)) / 2.; |
| 108 | } | ||
| 109 | else | ||
| 110 | { | ||
| 111 | static_assert(std::is_floating_point_v<T> || std::is_integral_v<T>, | ||
| 112 | "[TTS] TTS_ULP_EQUAL requires integral or floating points data to compare. " | ||
| 113 | "Did you mean to use TTS_ALL_ULP_EQUAL or to specialize " | ||
| 114 | "tts::precision<T>::ulp ?"); | ||
| 115 | return 0.; | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | 21 | static bool ieee(T const& a, T const& b) | |
| 120 | { | ||
| 121 | static_assert( | ||
| 122 | !requires { ieee_equal(a, b); }, | ||
| 123 | "[TTS] tts::ieee_equal is no longer a customization point. " | ||
| 124 | "Specialize tts::precision<T>::ieee instead."); | ||
| 125 | |||
| 126 |
5/6✓ Branch 0 taken 6 times.
✓ Branch 1 taken 6 times.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 1 times.
✓ Branch 6 taken 5 times.
✗ Branch 7 not taken.
|
21 | if constexpr(std::is_floating_point_v<T>) return (a == b) || (is_nan(a) && is_nan(b)); |
| 127 | else return eq(a, b); | ||
| 128 | } | ||
| 129 | }; | ||
| 130 | } | ||
| 131 | |||
| 132 | namespace tts | ||
| 133 | { | ||
| 134 | //==================================================================================================================== | ||
| 135 | /** | ||
| 136 | @defgroup tools-precision Precision Utilities | ||
| 137 | @{ | ||
| 138 | **/ | ||
| 139 | //==================================================================================================================== | ||
| 140 | |||
| 141 | //==================================================================================================================== | ||
| 142 | /*! | ||
| 143 | @ingroup tools-precision | ||
| 144 | @brief How far apart two values of type T are, and when they count as IEEE-equal | ||
| 145 | |||
| 146 | Specialize this for your own types rather than overloading the free functions: a specialization | ||
| 147 | that does not match is a compilation error, where a misnamed overload used to be ignored in | ||
| 148 | silence. Write the members the type needs and leave the rest out: a member the specialization | ||
| 149 | does not carry keeps its default. | ||
| 150 | |||
| 151 | A specialization that measures a compound type element by element calls tts::ulp_check, | ||
| 152 | tts::relative_check, tts::absolute_check and tts::ieee_check on the elements. Those carry the | ||
| 153 | dispatch; the trait itself answers only for a type that specializes it. | ||
| 154 | |||
| 155 | The `ulp_distance`, `relative_distance`, `absolute_distance` and `ieee_equal` free functions | ||
| 156 | this replaces are gone. An overload left behind is reported where it would have been used, | ||
| 157 | rather than ignored in silence. | ||
| 158 | **/ | ||
| 159 | //==================================================================================================================== | ||
| 160 | template<typename T> struct precision | ||
| 161 | { | ||
| 162 | }; | ||
| 163 | |||
| 164 | namespace _ | ||
| 165 | { | ||
| 166 | 1304 | template<typename T> inline double dispatch_absolute(T const& a, T const& b) | |
| 167 | { | ||
| 168 | 6 | if constexpr(requires { precision<T>::absolute(a, b); }) return precision<T>::absolute(a, b); | |
| 169 | else | ||
| 170 | { | ||
| 171 | static_assert( | ||
| 172 | !requires { precision<T>::absolute; }, | ||
| 173 | "[TTS] tts::precision<T>::absolute does not accept the two operands."); | ||
| 174 | 1298 | return builtin_precision<T>::absolute(a, b); | |
| 175 | } | ||
| 176 | } | ||
| 177 | |||
| 178 | 224 | template<typename T> inline double dispatch_relative(T const& a, T const& b) | |
| 179 | { | ||
| 180 | 6 | if constexpr(requires { precision<T>::relative(a, b); }) return precision<T>::relative(a, b); | |
| 181 | else | ||
| 182 | { | ||
| 183 | static_assert( | ||
| 184 | !requires { precision<T>::relative; }, | ||
| 185 | "[TTS] tts::precision<T>::relative does not accept the two operands."); | ||
| 186 | 218 | return builtin_precision<T>::relative(a, b); | |
| 187 | } | ||
| 188 | } | ||
| 189 | |||
| 190 | 16588 | template<typename T> inline double dispatch_ulp(T const& a, T const& b) | |
| 191 | { | ||
| 192 | 6 | if constexpr(requires { precision<T>::ulp(a, b); }) return precision<T>::ulp(a, b); | |
| 193 | else | ||
| 194 | { | ||
| 195 | static_assert( | ||
| 196 | !requires { precision<T>::ulp; }, | ||
| 197 | "[TTS] tts::precision<T>::ulp does not accept the two operands."); | ||
| 198 | 16582 | return builtin_precision<T>::ulp(a, b); | |
| 199 | } | ||
| 200 | } | ||
| 201 | |||
| 202 | 29 | template<typename T> inline bool dispatch_ieee(T const& a, T const& b) | |
| 203 | { | ||
| 204 | 6 | if constexpr(requires { precision<T>::ieee(a, b); }) return precision<T>::ieee(a, b); | |
| 205 | else | ||
| 206 | { | ||
| 207 | static_assert( | ||
| 208 | !requires { precision<T>::ieee; }, | ||
| 209 | "[TTS] tts::precision<T>::ieee does not accept the two operands."); | ||
| 210 | 23 | return builtin_precision<T>::ieee(a, b); | |
| 211 | } | ||
| 212 | } | ||
| 213 | |||
| 214 | // A finite relative tolerance of one or more accepts any error up to the value itself, which | ||
| 215 | // is never what a test means: it is a percentage left over from TTS 3, where the distance was | ||
| 216 | // reported as one. An infinite tolerance is deliberate, it accepts the nan and infinity | ||
| 217 | // cases. Only the native floating point path can say so; a precision specialisation of its | ||
| 218 | // own has its own unit. | ||
| 219 | template<typename T> | ||
| 220 | concept native_precision = !requires { precision<T>::relative; }; | ||
| 221 | |||
| 222 | 94 | template<typename T, typename N> constexpr bool reads_as_percent(N const& n) | |
| 223 | { | ||
| 224 | using type = std::remove_cvref_t<T>; | ||
| 225 | if constexpr(native_precision<type> && std::is_arithmetic_v<type> && | ||
| 226 | !std::is_same_v<type, bool>) | ||
| 227 |
4/4✓ Branch 0 taken 18 times.
✓ Branch 1 taken 30 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 16 times.
|
81 | return n >= 1 && n != std::numeric_limits<N>::infinity(); |
| 228 | 13 | else return false; | |
| 229 | } | ||
| 230 | } | ||
| 231 | |||
| 232 | //==================================================================================================================== | ||
| 233 | /*! | ||
| 234 | @ingroup tools-precision | ||
| 235 | @public | ||
| 236 | @brief Compute the absolute distance between two values | ||
| 237 | |||
| 238 | @note This functions is meant to help implementing @ref TTS_ABSOLUTE_EQUAL support for custom | ||
| 239 | types. For testing purposes, use @ref TTS_ABSOLUTE_EQUAL directly. See @ref customize-precision | ||
| 240 | for more details. | ||
| 241 | |||
| 242 | @param a Value to compare | ||
| 243 | @param b Value to compare | ||
| 244 | @return The distance between a and b | ||
| 245 | **/ | ||
| 246 | //==================================================================================================================== | ||
| 247 | 1304 | template<typename T, typename U> inline double absolute_check(T const& a, U const& b) | |
| 248 | { | ||
| 249 | static_assert(std::is_same_v<T, U>, | ||
| 250 | "[TTS] TTS_ABSOLUTE_EQUAL needs both operands to have the same type. " | ||
| 251 | "Comparing through their common type would express the distance in the unit " | ||
| 252 | "of the promoted type, which is not the one being tested. Convert the " | ||
| 253 | "expected value at the call site instead."); | ||
| 254 | 1304 | if constexpr(std::is_same_v<T, U>) return _::dispatch_absolute(a, b); | |
| 255 | else return 0.; | ||
| 256 | } | ||
| 257 | |||
| 258 | //==================================================================================================================== | ||
| 259 | /*! | ||
| 260 | @ingroup tools-precision | ||
| 261 | @public | ||
| 262 | @brief Compute the relative distance between two values | ||
| 263 | |||
| 264 | @note This functions is meant to help implementing @ref TTS_RELATIVE_EQUAL support for custom | ||
| 265 | types. For testing purposes, use @ref TTS_RELATIVE_EQUAL directly. See @ref customize-precision | ||
| 266 | for more details. | ||
| 267 | |||
| 268 | @param a Value to compare | ||
| 269 | @param b Value to compare | ||
| 270 | @return The relative distance between a and b | ||
| 271 | **/ | ||
| 272 | //==================================================================================================================== | ||
| 273 | 224 | template<typename T, typename U> inline double relative_check(T const& a, U const& b) | |
| 274 | { | ||
| 275 | static_assert(std::is_same_v<T, U>, | ||
| 276 | "[TTS] TTS_RELATIVE_EQUAL needs both operands to have the same type. " | ||
| 277 | "Comparing through their common type would express the distance in the unit " | ||
| 278 | "of the promoted type, which is not the one being tested. Convert the " | ||
| 279 | "expected value at the call site instead."); | ||
| 280 | 224 | if constexpr(std::is_same_v<T, U>) return _::dispatch_relative(a, b); | |
| 281 | else return 0.; | ||
| 282 | } | ||
| 283 | |||
| 284 | //==================================================================================================================== | ||
| 285 | /*! | ||
| 286 | @ingroup tools-precision | ||
| 287 | @public | ||
| 288 | @brief Compute the distance in ULP between two values | ||
| 289 | |||
| 290 | @note This functions is meant to help implementing @ref TTS_ULP_EQUAL support for custom types. | ||
| 291 | For testing purposes, use @ref TTS_ULP_EQUAL directly. See @ref customize-precision for | ||
| 292 | more details. | ||
| 293 | |||
| 294 | @param a Value to compare | ||
| 295 | @param b Value to compare | ||
| 296 | @return The distance in ULP between a and b | ||
| 297 | **/ | ||
| 298 | //==================================================================================================================== | ||
| 299 | 16588 | template<typename T, typename U> inline double ulp_check(T const& a, U const& b) | |
| 300 | { | ||
| 301 | static_assert(std::is_same_v<T, U>, | ||
| 302 | "[TTS] TTS_ULP_EQUAL needs both operands to have the same type. " | ||
| 303 | "Comparing through their common type would express the distance in the unit " | ||
| 304 | "of the promoted type, which is not the one being tested. Convert the " | ||
| 305 | "expected value at the call site instead."); | ||
| 306 | 16588 | if constexpr(std::is_same_v<T, U>) return _::dispatch_ulp(a, b); | |
| 307 | else return 0.; | ||
| 308 | } | ||
| 309 | |||
| 310 | //==================================================================================================================== | ||
| 311 | /*! | ||
| 312 | @ingroup tools-precision | ||
| 313 | @public | ||
| 314 | @brief Compute if values are exactly equals or all NaNs/Invalids | ||
| 315 | |||
| 316 | @note This functions is meant to help implementing @ref TTS_IEEE_EQUAL support for custom types. | ||
| 317 | For testing purposes, use @ref TTS_IEEE_EQUAL directly. See @ref customize-precision for | ||
| 318 | more details. | ||
| 319 | |||
| 320 | @param a Value to compare | ||
| 321 | @param b Value to compare | ||
| 322 | @return Whether `a == b`, or both are NaN | ||
| 323 | **/ | ||
| 324 | //==================================================================================================================== | ||
| 325 | 33 | template<typename T, typename U> inline bool ieee_check(T const& a, U const& b) | |
| 326 | { | ||
| 327 | // Left permissive on the pair of types, unlike the three distances: this one manufactures no | ||
| 328 | // conversion, it ends on eq(a, b). Two different types here are the TTS_EQUAL case, not the | ||
| 329 | // precision one, and neither of them can name a single tts::precision to answer. | ||
| 330 | 29 | if constexpr(std::is_same_v<T, U>) return _::dispatch_ieee(a, b); | |
| 331 | else if constexpr(std::is_floating_point_v<T>) | ||
| 332 | return (a == b) || (_::is_nan(a) && _::is_nan(b)); | ||
| 333 | 4 | else return _::eq(a, b); | |
| 334 | } | ||
| 335 | //==================================================================================================================== | ||
| 336 | //! @} | ||
| 337 | //==================================================================================================================== | ||
| 338 | } | ||
| 339 |