GCC Code Coverage Report


Directory: include/tts/
File: include/tts/tools/precision.hpp
Date: 2026-10-14 16:28:14
Exec Total Coverage
Lines: 53 53 100.0%
Functions: 143 143 100.0%
Branches: 67 68 98.5%

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