GCC Code Coverage Report


Directory: include/tts/
File: include/tts/tools/random.hpp
Date: 2026-10-14 16:28:14
Exec Total Coverage
Lines: 61 69 88.4%
Functions: 34 34 100.0%
Branches: 27 46 58.7%

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 #include <tts/engine/math.hpp>
12
13 namespace tts
14 {
15 //====================================================================================================================
16 /**
17 @defgroup tools-random Utilities
18 @{
19 **/
20 //====================================================================================================================
21
22 namespace _
23 {
24 // SplitMix64 PRNG State
25 inline thread_local std::uint64_t prng_state = 0x853c49e6748fea9bULL;
26
27 // SplitMix64 generator: extremely fast, statistically robust, reproducible
28 1888338 inline std::uint64_t next_random()
29 {
30 1888338 std::uint64_t z = (prng_state += 0x9e3779b97f4a7c15ULL);
31 1888338 z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL;
32 1888338 z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL;
33 1888338 return z ^ (z >> 31);
34 }
35
36 // Unbiased Integer Roll
37 // Uses Rejection Sampling to fix modulo bias and extend range for full 64-bit capability
38 1532208 template<std::integral T> T roll(T M, T N)
39 {
40
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 816102 times.
1532208 if(M == N) return M;
41
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 816102 times.
1532196 if(M > N) std::swap(M, N);
42
43 using U = std::make_unsigned_t<T>;
44 1532196 auto diff = static_cast<U>(static_cast<U>(N) - static_cast<U>(M));
45
46 // If the range spans the absolute maximum of the data type (e.g., 0 to UINT64_MAX)
47
2/2
✓ Branch 1 taken 400010 times.
✓ Branch 2 taken 416092 times.
1532196 if(diff == std::numeric_limits<U>::max())
48 {
49 800020 return static_cast<T>(next_random());
50 }
51
52 732176 auto range = static_cast<U>(diff + 1);
53 732176 std::uint64_t r_max = std::numeric_limits<std::uint64_t>::max();
54 732176 std::uint64_t bucket_size = r_max / range;
55 732176 std::uint64_t limit = bucket_size * range;
56
57 std::uint64_t r;
58 do
59 {
60 732176 r = next_random();
61
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 416092 times.
732176 } while(r >= limit);
62
63 732176 return static_cast<T>(M + static_cast<T>(r / bucket_size));
64 }
65
66 // High-Resolution Float Roll
67 // Uses full 64-bit precision to avoid quantization errors (step artifacts)
68 2136280 template<std::floating_point T> T roll(T M, T N)
69 {
70
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1072236 times.
2136280 if(M == N) return M;
71
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1072236 times.
2136280 if(M > N) std::swap(M, N);
72
73 // Normalize exactly to [0, 1] based on full 64-bit precision
74 2136280 T uniform_01 =
75 2136280 static_cast<T>(next_random()) / static_cast<T>(std::numeric_limits<std::uint64_t>::max());
76
77 // Use proportional interpolation to avoid N - M overflow
78 // (e.g., when N = numeric_limits::max and M = numeric_limits::lowest)
79 2136280 return (T(1.0) - uniform_01) * M + uniform_01 * N;
80 }
81
82 1532208 template<std::integral T> T roll_random(T mini, T maxi)
83 {
84 1532208 return _::roll(mini, maxi);
85 }
86
87 // The floor a log-uniform roll starts from, for a range entirely above zero. It only moves when
88 // mini is the placeholder rather than a bound the caller wrote: smvlp is what the zero-crossing
89 // branch recurses with, and what an explicit 0 becomes. Rolling from there up to 1 would
90 // practically never come near 1, hence the lift. A bound the caller asked for is the range they
91 // want tested, so it stays.
92 1268162 template<std::floating_point T> T rolling_floor(T mini, T maxi)
93 {
94 1268162 constexpr T smvlp = std::numeric_limits<T>::min();
95 1268162 constexpr T eps = std::numeric_limits<T>::epsilon();
96
97
2/2
✓ Branch 0 taken 200000 times.
✓ Branch 1 taken 436129 times.
1268162 if(mini != smvlp) return mini;
98
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 436129 times.
868162 if(maxi == 1) return eps;
99
1/2
✓ Branch 0 taken 436129 times.
✗ Branch 1 not taken.
868162 if(maxi > 1) return max(T(1) / _::sqrt(maxi), mini);
100 ✗ return mini;
101 }
102
103 // Floating point complex logic (Logarithmic distribution)
104 2136292 template<std::floating_point T> T roll_random(T mini, T maxi)
105 {
106 2136292 constexpr T smvlp = std::numeric_limits<T>::min();
107 2136292 constexpr T valmax = std::numeric_limits<T>::max();
108 2136292 constexpr T quiet_nan = std::numeric_limits<T>::quiet_NaN();
109
110 // Degenerate range: the only value in [mini,maxi] is mini itself
111
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1072236 times.
2136292 if(mini == maxi) return mini;
112
113 // Sanitize 0
114
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 1072214 times.
2136280 if(mini == 0) mini = smvlp;
115
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1072236 times.
2136280 if(maxi == 0) maxi = -smvlp;
116
117 // Fix Infinite inputs (Prevent NaN propagation)
118
2/6
✗ Branch 1 not taken.
✓ Branch 2 taken 1072236 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 1072236 times.
2136280 if(_::is_inf(mini) && mini < 0) mini = -valmax;
119
2/6
✗ Branch 1 not taken.
✓ Branch 2 taken 1072236 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 1072236 times.
2136280 if(_::is_inf(maxi) && maxi > 0) maxi = valmax;
120
121 // Handle Zero-Crossing Ranges [-A, +B]
122
3/4
✓ Branch 0 taken 436107 times.
✓ Branch 1 taken 636129 times.
✓ Branch 2 taken 436107 times.
✗ Branch 3 not taken.
2136280 if(mini < 0 && maxi > 0)
123 {
124 868118 T abs_min = _::abs(mini);
125 868118 T abs_max = _::abs(maxi);
126
127 // Weight probability by magnitude
128 868118 T total_mag = abs_min + abs_max;
129 868118 bool pick_positive = _::roll(T(0.0), total_mag) < abs_max;
130
131
2/2
✓ Branch 0 taken 277697 times.
✓ Branch 1 taken 158410 times.
868118 if(pick_positive) return roll_random(smvlp, maxi);
132 314791 else return -roll_random(smvlp, abs_min);
133 }
134
135 1268162 T value = {};
136
137 // Handle Single-Sided Ranges
138
1/2
✓ Branch 0 taken 636129 times.
✗ Branch 1 not taken.
1268162 if(mini > 0)
139 {
140 1268162 mini = rolling_floor(mini, maxi);
141
142 1268162 T log_min = _::log10(mini);
143 1268162 T log_max = _::log10(maxi);
144
145 // Roll in log-space
146 1268162 T log_val = _::roll(log_min, log_max);
147 1268162 value = _::exp10(log_val);
148 }
149 ✗ else if(maxi < 0)
150 {
151 // Mirrored: the same floor, applied to the magnitudes.
152 ✗ maxi = -rolling_floor(-maxi, -mini);
153
154 ✗ T log_min = _::log10(-maxi);
155 ✗ T log_max = _::log10(-mini);
156
157 ✗ T log_val = _::roll(log_min, log_max);
158 ✗ value = -_::exp10(log_val);
159 }
160 else
161 {
162 ✗ return quiet_nan;
163 }
164
165 1268162 return value;
166 }
167 }
168
169 //====================================================================================================================
170 /**
171 @public
172 @brief Sets the seed for the internal PRNG to guarantee reproducibility.
173 @ingroup tools-random
174
175 @param seed The 64-bit seed value to initialize the generator state.
176 **/
177 //====================================================================================================================
178 99 inline void set_random_seed(std::uint64_t seed)
179 {
180 99 _::prng_state = seed;
181 99 }
182
183 //====================================================================================================================
184 /**
185 @public
186 @brief Generates a random value between two bounds.
187 @ingroup tools-random
188
189 This function generates a random value of type `T` between the specified `mini` and `maxi`
190 bounds using an uniform distribution for integral types and a log-uniform distribution for
191 floating-point types.
192
193 @groupheader{Example}
194 @snippet doc/random.cpp snippet
195
196 @param mini Lower bound for the random value generation.
197 @param maxi Upper bound for the random value generation.
198 @return A random value of type `T` between `mini` and `maxi`.
199
200 @see tts::randoms
201 **/
202 //====================================================================================================================
203 2900390 template<typename T> T random_value(T mini, T maxi)
204 {
205 2900390 return _::roll_random(mini, maxi);
206 }
207 }
208