blob: 0a95cb84c5513ce6e6457480b0f539523eab4c9f (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
// Copyright (c) 2026 Progmasoft.
// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
#pragma once
#include <bit>
#include <concepts>
#include <cstdint>
#include <limits>
#include <stdexcept>
#include <type_traits>
namespace Progmasoft::Catch3 {
/// Small, deterministic random source for repeatable property-test cases.
///
/// The SplitMix64 sequence is specified here instead of delegating to a
/// standard-library engine, whose output sequence is not portable between
/// library implementations. A Random object is intentionally not
/// thread-safe; property trials receive independent instances derived from
/// the root seed.
class Random final {
public:
explicit constexpr Random( std::uint64_t seed ) noexcept:
state_( seed ) {}
/// Returns the next value in the stable SplitMix64 sequence.
[[nodiscard]] constexpr std::uint64_t Next() noexcept {
state_ += kGamma;
std::uint64_t value = state_;
value = ( value ^ ( value >> 30U ) ) * kMultiplierOne;
value = ( value ^ ( value >> 27U ) ) * kMultiplierTwo;
return value ^ ( value >> 31U );
}
/// Returns a value in [0, bound); bound == 0 requests the full uint64
/// range.
[[nodiscard]] constexpr std::uint64_t
Below( std::uint64_t bound ) noexcept {
if ( bound == 0 ) { return Next(); }
// Rejection sampling avoids the modulo bias of a single `% bound`.
const std::uint64_t threshold =
( std::uint64_t{ 0 } - bound ) % bound;
while ( true ) {
const std::uint64_t value = Next();
if ( value >= threshold ) { return value % bound; }
}
}
/// Returns an inclusive integral value without overflowing at type
/// limits.
template <std::integral IntegerType>
requires( !std::same_as<IntegerType, bool> ) [[nodiscard]] IntegerType
Between( IntegerType minimum, IntegerType maximum ) {
if ( minimum > maximum ) {
throw std::invalid_argument(
"Random::Between requires minimum <= maximum" );
}
using UnsignedType = std::make_unsigned_t<IntegerType>;
static_assert( std::numeric_limits<UnsignedType>::digits <= 64 );
constexpr unsigned kBitCount = static_cast<unsigned>(
std::numeric_limits<UnsignedType>::digits );
constexpr UnsignedType kSignBit = UnsignedType{ 1 }
<< ( kBitCount - 1U );
const auto encode =
[]( IntegerType value ) constexpr -> UnsignedType {
if constexpr ( std::is_signed_v<IntegerType> ) {
return std::bit_cast<UnsignedType>( value ) ^ kSignBit;
} else {
return value;
}
};
const auto decode =
[]( UnsignedType value ) constexpr -> IntegerType {
if constexpr ( std::is_signed_v<IntegerType> ) {
return std::bit_cast<IntegerType>( value ^ kSignBit );
} else {
return value;
}
};
const UnsignedType lower = encode( minimum );
const UnsignedType upper = encode( maximum );
// Unsigned wrap to zero represents the complete value domain.
const UnsignedType width =
static_cast<UnsignedType>( upper - lower + UnsignedType{ 1 } );
const UnsignedType offset =
width == 0 ? static_cast<UnsignedType>( Next() )
: static_cast<UnsignedType>(
Below( static_cast<std::uint64_t>( width ) ) );
return decode( static_cast<UnsignedType>( lower + offset ) );
}
/// Derives an independent, reproducible seed for one trial.
[[nodiscard]] static constexpr std::uint64_t
DeriveSeed( std::uint64_t rootSeed,
std::uint64_t trialIndex ) noexcept {
Random trialRandom( rootSeed + kGamma * ( trialIndex + 1U ) );
return trialRandom.Next();
}
private:
static constexpr std::uint64_t kGamma = 0x9E3779B97F4A7C15ULL;
static constexpr std::uint64_t kMultiplierOne = 0xBF58476D1CE4E5B9ULL;
static constexpr std::uint64_t kMultiplierTwo = 0x94D049BB133111EBULL;
std::uint64_t state_;
};
} // namespace Progmasoft::Catch3
|