| 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 |