dasmig-core 1.2.0
Shared random engine, distributions and data tiers for the dasmig generator libraries
Loading...
Searching...
No Matches
rng.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2020-2026 Diego Dasso Migotto
3
4#ifndef DASMIG_CORE_RNG_HPP
5#define DASMIG_CORE_RNG_HPP
6
7#include <array>
8#include <bit>
9#include <cstdint>
10#include <limits>
11#include <random>
12#include <string_view>
13
14/// @file rng.hpp
15/// @brief The random engine every generator library uses: xoshiro256**
16/// seeded through SplitMix64, with labelled child streams.
17/// @author Diego Dasso Migotto (diegomigotto at hotmail dot com)
18///
19/// The algorithms are fixed and the output is part of the core's contract:
20/// a seed gives the same sequence on every compiler and platform. Changing
21/// any of them is a major version change.
22
23namespace dasmig::core
24{
25
26/// @brief The SplitMix64 output function (a bijection on 64-bit values).
27/// @param z Input value.
28/// @return The mixed value.
29[[nodiscard]] constexpr std::uint64_t splitmix64_mix(std::uint64_t z) noexcept
30{
31 z = (z ^ (z >> 30U)) * 0xbf58476d1ce4e5b9ULL;
32 z = (z ^ (z >> 27U)) * 0x94d049bb133111ebULL;
33 return z ^ (z >> 31U);
34}
35
36/// @brief The SplitMix64 generator (Steele, Lea and Flood 2014).
37///
38/// Used to expand a 64-bit seed into the 256-bit xoshiro state, as the
39/// xoshiro authors recommend.
41{
42 public:
43 /// @brief Construct from a seed.
44 /// @param seed Initial state.
45 constexpr explicit splitmix64(std::uint64_t seed) noexcept : _state{seed} {}
46
47 /// @brief Advance and return the next value.
48 /// @return The next 64-bit value.
49 constexpr std::uint64_t operator()() noexcept
50 {
51 _state += 0x9e3779b97f4a7c15ULL;
52 return splitmix64_mix(_state);
53 }
54
55 private:
56 std::uint64_t _state;
57};
58
59/// @brief 64-bit FNV-1a hash of the bytes of a string.
60///
61/// Labels are hashed as bytes, so pass UTF-8: a `std::wstring` label would
62/// hash differently on Windows (UTF-16) and elsewhere (UTF-32).
63///
64/// @param text Bytes to hash.
65/// @return The hash.
66[[nodiscard]] constexpr std::uint64_t fnv1a64(std::string_view text) noexcept
67{
68 std::uint64_t hash = 0xcbf29ce484222325ULL;
69 for (const char c : text)
70 {
71 hash ^= static_cast<std::uint8_t>(c);
72 hash *= 0x100000001b3ULL;
73 }
74 return hash;
75}
76
77/// @brief Combine a hash with another 64-bit value, order-sensitively.
78///
79/// `splitmix64_mix(hash ^ splitmix64_mix(value))`, the same construction as
80/// rng::child_seed().
81///
82/// @param hash Running hash.
83/// @param value Value to fold in.
84/// @return The combined hash.
85[[nodiscard]] constexpr std::uint64_t hash_combine(std::uint64_t hash,
86 std::uint64_t value) noexcept
87{
88 return splitmix64_mix(hash ^ splitmix64_mix(value));
89}
90
91/// @brief The core random engine: xoshiro256** (Blackman and Vigna 2018).
92///
93/// Satisfies `std::uniform_random_bit_generator`, but use the core's own
94/// distributions with it: the `std::` distributions give different results
95/// on different standard libraries.
96///
97/// An engine remembers the seed it was built from. split() derives a child
98/// engine from that seed and a label, not from the current state, so the
99/// streams a generator hands to its parts do not shift when another part
100/// draws more or fewer numbers.
101///
102/// @code
103/// dasmig::core::rng r{42};
104/// auto height = r.split("height"); // same stream for seed 42, always
105/// auto weight = r.split("weight"); // independent of height's draws
106/// @endcode
107class rng
108{
109 public:
110 using result_type = std::uint64_t; ///< Type of the values produced.
111
112 /// @brief Construct from a seed.
113 /// @param seed Seed; every value, including 0, is a valid seed.
114 constexpr explicit rng(std::uint64_t seed) noexcept { this->seed(seed); }
115
116 /// @brief Construct with a seed taken from `std::random_device`.
117 ///
118 /// For the instance engines of non-seeded calls only; anything that must
119 /// replay takes a seed explicitly.
120 ///
121 /// @return An engine with an unpredictable seed.
122 [[nodiscard]] static rng from_entropy() { return rng{entropy_seed()}; }
123
124 /// @brief A 64-bit seed from `std::random_device`.
125 /// @return An unpredictable seed.
126 [[nodiscard]] static std::uint64_t entropy_seed()
127 {
128 std::random_device device;
129 const auto hi = static_cast<std::uint64_t>(device());
130 const auto lo = static_cast<std::uint64_t>(device());
131 return (hi << 32U) ^ lo;
132 }
133
134 /// @brief Reset the engine to the start of the sequence for a seed.
135 /// @param seed The new seed.
136 /// @return `*this`.
137 constexpr rng& seed(std::uint64_t seed) noexcept
138 {
139 _seed = seed;
140 splitmix64 expand{seed};
141 for (auto& word : _s)
142 {
143 word = expand();
144 }
145 return *this;
146 }
147
148 /// @brief Reseed from `std::random_device`.
149 /// @return `*this`.
150 rng& unseed() { return seed(entropy_seed()); }
151
152 /// @brief The seed this engine was last seeded with.
153 /// @return The seed; constructing an engine from it replays the sequence.
154 [[nodiscard]] constexpr std::uint64_t seed() const noexcept { return _seed; }
155
156 /// @brief Advance and return the next value.
157 /// @return A uniformly distributed 64-bit value.
158 constexpr result_type operator()() noexcept
159 {
160 const std::uint64_t result = std::rotl(_s[1] * 5U, 7) * 9U;
161 const std::uint64_t t = _s[1] << 17U;
162 _s[2] ^= _s[0];
163 _s[3] ^= _s[1];
164 _s[1] ^= _s[2];
165 _s[0] ^= _s[3];
166 _s[2] ^= t;
167 _s[3] = std::rotl(_s[3], 45);
168 return result;
169 }
170
171 /// @brief Derive the child seed for a label.
172 ///
173 /// `splitmix64_mix(seed ^ splitmix64_mix(fnv1a64(label)))`.
174 ///
175 /// @param label UTF-8 label naming the child stream.
176 /// @return The child seed.
177 [[nodiscard]] constexpr std::uint64_t child_seed(
178 std::string_view label) const noexcept
179 {
180 return splitmix64_mix(_seed ^ splitmix64_mix(fnv1a64(label)));
181 }
182
183 /// @brief Derive the child seed for an index.
184 ///
185 /// `splitmix64_mix(seed ^ splitmix64_mix(index ^ 0x6a09e667f3bcc909))`;
186 /// the constant keeps index streams apart from label streams.
187 ///
188 /// @param index Index of the child stream (for example a family member).
189 /// @return The child seed.
190 [[nodiscard]] constexpr std::uint64_t child_seed(
191 std::uint64_t index) const noexcept
192 {
193 return child_seed_of(_seed, index);
194 }
195
196 /// @brief child_seed(index) for a seed, without building an engine.
197 ///
198 /// Building an engine expands the seed into 256 bits of state; callers
199 /// that only need the derived seed (item_seed()) skip that.
200 ///
201 /// @param seed Parent seed.
202 /// @param index Index of the child stream.
203 /// @return The same value as `rng{seed}.child_seed(index)`.
204 [[nodiscard]] static constexpr std::uint64_t child_seed_of(
205 std::uint64_t seed, std::uint64_t index) noexcept
206 {
207 return splitmix64_mix(seed ^ splitmix64_mix(index ^ 0x6a09e667f3bcc909ULL));
208 }
209
210 /// @brief A child engine for a label, independent of this engine's state.
211 /// @param label UTF-8 label naming the child stream.
212 /// @return An engine seeded with child_seed(label).
213 [[nodiscard]] constexpr rng split(std::string_view label) const noexcept
214 {
215 return rng{child_seed(label)};
216 }
217
218 /// @brief A child engine for an index, independent of this engine's state.
219 /// @param index Index of the child stream.
220 /// @return An engine seeded with child_seed(index).
221 [[nodiscard]] constexpr rng split(std::uint64_t index) const noexcept
222 {
223 return rng{child_seed(index)};
224 }
225
226 /// @brief Smallest value produced.
227 /// @return 0.
228 [[nodiscard]] static constexpr result_type min() noexcept { return 0; }
229
230 /// @brief Largest value produced.
231 /// @return 2^64 - 1.
232 [[nodiscard]] static constexpr result_type max() noexcept
233 {
234 return std::numeric_limits<result_type>::max();
235 }
236
237 /// @brief Engines are equal when their seed and position are equal.
238 friend constexpr bool operator==(const rng&, const rng&) noexcept = default;
239
240 private:
241 std::array<std::uint64_t, 4> _s{};
242 std::uint64_t _seed{0};
243};
244
245static_assert(std::uniform_random_bit_generator<rng>);
246
247} // namespace dasmig::core
248
249#endif // DASMIG_CORE_RNG_HPP
The core random engine: xoshiro256** (Blackman and Vigna 2018).
Definition rng.hpp:108
static constexpr result_type max() noexcept
Largest value produced.
Definition rng.hpp:232
constexpr std::uint64_t child_seed(std::uint64_t index) const noexcept
Derive the child seed for an index.
Definition rng.hpp:190
static rng from_entropy()
Construct with a seed taken from std::random_device.
Definition rng.hpp:122
constexpr rng split(std::string_view label) const noexcept
A child engine for a label, independent of this engine's state.
Definition rng.hpp:213
constexpr rng(std::uint64_t seed) noexcept
Construct from a seed.
Definition rng.hpp:114
static std::uint64_t entropy_seed()
A 64-bit seed from std::random_device.
Definition rng.hpp:126
std::uint64_t result_type
Type of the values produced.
Definition rng.hpp:110
constexpr result_type operator()() noexcept
Advance and return the next value.
Definition rng.hpp:158
constexpr rng split(std::uint64_t index) const noexcept
A child engine for an index, independent of this engine's state.
Definition rng.hpp:221
constexpr std::uint64_t child_seed(std::string_view label) const noexcept
Derive the child seed for a label.
Definition rng.hpp:177
rng & unseed()
Reseed from std::random_device.
Definition rng.hpp:150
constexpr rng & seed(std::uint64_t seed) noexcept
Reset the engine to the start of the sequence for a seed.
Definition rng.hpp:137
constexpr std::uint64_t seed() const noexcept
The seed this engine was last seeded with.
Definition rng.hpp:154
static constexpr std::uint64_t child_seed_of(std::uint64_t seed, std::uint64_t index) noexcept
child_seed(index) for a seed, without building an engine.
Definition rng.hpp:204
static constexpr result_type min() noexcept
Smallest value produced.
Definition rng.hpp:228
friend constexpr bool operator==(const rng &, const rng &) noexcept=default
Engines are equal when their seed and position are equal.
The SplitMix64 generator (Steele, Lea and Flood 2014).
Definition rng.hpp:41
constexpr std::uint64_t operator()() noexcept
Advance and return the next value.
Definition rng.hpp:49
constexpr splitmix64(std::uint64_t seed) noexcept
Construct from a seed.
Definition rng.hpp:45
constexpr std::uint64_t hash_combine(std::uint64_t hash, std::uint64_t value) noexcept
Combine a hash with another 64-bit value, order-sensitively.
Definition rng.hpp:85
constexpr std::uint64_t fnv1a64(std::string_view text) noexcept
64-bit FNV-1a hash of the bytes of a string.
Definition rng.hpp:66
constexpr std::uint64_t splitmix64_mix(std::uint64_t z) noexcept
The SplitMix64 output function (a bijection on 64-bit values).
Definition rng.hpp:29