Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/src/Progmasoft/Catch3/Random.hpp
blob: 31e9dd1d286954e410a84781afae8b8754d0c30f (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
114
115
116
117
118
119
// Copyright (c) 2026 Progmasoft.
// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
#pragma once

#include <Progmasoft/Catch3/Detail/InvalidArgument.hpp>
#include <Progmasoft/Catch3/Detail/TypeTraits.hpp>
#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<typename IntegerType, Detail::EnableIfInteger<IntegerType> = 0>
        [[nodiscard]] IntegerType
        Between(IntegerType minimum, IntegerType maximum)
        {
            if (minimum > maximum)
            {
                Detail::RejectInvalidArgument(
                    "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);

            // Flipping the sign bit orders signed values as unsigned ones.
            const UnsignedType flip
                = std::is_signed_v<IntegerType> ? kSignBit : UnsignedType{};
            const auto lower
                = static_cast<UnsignedType>(static_cast<UnsignedType>(minimum)
                                            ^ flip);
            const auto upper
                = static_cast<UnsignedType>(static_cast<UnsignedType>(maximum)
                                            ^ flip);
            // 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 static_cast<IntegerType>(
                static_cast<UnsignedType>(lower + offset) ^ flip);
        }

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