-
-
Save imneme/540829265469e673d045 to your computer and use it in GitHub Desktop.
/* | |
* Random-Number Utilities (randutil) | |
* Addresses common issues with C++11 random number generation. | |
* Makes good seeding easier, and makes using RNGs easy while retaining | |
* all the power. | |
* | |
* The MIT License (MIT) | |
* | |
* Copyright (c) 2015-2022 Melissa E. O'Neill | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy | |
* of this software and associated documentation files (the "Software"), to deal | |
* in the Software without restriction, including without limitation the rights | |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
* copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in | |
* all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
* SOFTWARE. | |
*/ | |
#ifndef RANDUTILS_HPP | |
#define RANDUTILS_HPP 1 | |
/* | |
* This header includes three class templates that can help make C++11 | |
* random number generation easier to use. | |
* | |
* randutils::seed_seq_fe | |
* | |
* Fixed-Entropy Seed sequence | |
* | |
* Provides a replacement for std::seed_seq that avoids problems with bias, | |
* performs better in empirical statistical tests, and executes faster in | |
* normal-sized use cases. | |
* | |
* In normal use, it's accessed via one of the following type aliases | |
* | |
* randutils::seed_seq_fe128 | |
* randutils::seed_seq_fe256 | |
* | |
* It's discussed in detail at | |
* http://www.pcg-random.org/posts/developing-a-seed_seq-alternative.html | |
* and the motivation for its creation (what's wrong with std::seed_seq) here | |
* http://www.pcg-random.org/posts/cpp-seeding-surprises.html | |
* | |
* | |
* randutils::auto_seeded | |
* | |
* Extends a seed sequence class with a nondeterministic default constructor. | |
* Uses a variety of local sources of entropy to portably initialize any | |
* seed sequence to a good default state. | |
* | |
* In normal use, it's accessed via one of the following type aliases, which | |
* use seed_seq_fe128 and seed_seq_fe256 above. | |
* | |
* randutils::auto_seed_128 | |
* randutils::auto_seed_256 | |
* | |
* It's discussed in detail at | |
* http://www.pcg-random.org/posts/simple-portable-cpp-seed-entropy.html | |
* and its motivation (why you can't just use std::random_device) here | |
* http://www.pcg-random.org/posts/cpps-random_device.html | |
* | |
* | |
* randutils::random_generator | |
* | |
* An Easy-to-Use Random API | |
* | |
* Provides all the power of C++11's random number facility in an easy-to | |
* use wrapper. | |
* | |
* In normal use, it's accessed via one of the following type aliases, which | |
* also use auto_seed_256 by default | |
* | |
* randutils::default_rng | |
* randutils::mt19937_rng | |
* | |
* It's discussed in detail at | |
* http://www.pcg-random.org/posts/ease-of-use-without-loss-of-power.html | |
*/ | |
#include <cstddef> | |
#include <cstdint> | |
#include <cstdlib> | |
#include <random> | |
#include <array> | |
#include <functional> // for std::hash | |
#include <initializer_list> | |
#include <utility> | |
#include <type_traits> | |
#include <iterator> | |
#include <chrono> | |
#include <thread> | |
#include <algorithm> | |
// Ugly platform-specific code for auto_seeded | |
#if !defined(RANDUTILS_CPU_ENTROPY) && defined(__has_builtin) | |
#if __has_builtin(__builtin_readcyclecounter) && !defined(__aarch64__) | |
#define RANDUTILS_CPU_ENTROPY __builtin_readcyclecounter() | |
#endif | |
#endif | |
#if !defined(RANDUTILS_CPU_ENTROPY) | |
#if __i386__ | |
#if __GNUC__ | |
#define RANDUTILS_CPU_ENTROPY __builtin_ia32_rdtsc() | |
#else | |
#include <immintrin.h> | |
#define RANDUTILS_CPU_ENTROPY __rdtsc() | |
#endif | |
#else | |
#define RANDUTILS_CPU_ENTROPY 0 | |
#endif | |
#endif | |
#if defined(RANDUTILS_GETPID) | |
// Already defined externally | |
#elif defined(_WIN64) || defined(_WIN32) | |
#include <process.h> | |
#define RANDUTILS_GETPID _getpid() | |
#elif defined(__unix__) || defined(__unix) \ | |
|| (defined(__APPLE__) && defined(__MACH__)) | |
#include <unistd.h> | |
#define RANDUTILS_GETPID getpid() | |
#else | |
#define RANDUTILS_GETPID 0 | |
#endif | |
#if __cpp_constexpr >= 201304L | |
#define RANDUTILS_GENERALIZED_CONSTEXPR constexpr | |
#else | |
#define RANDUTILS_GENERALIZED_CONSTEXPR | |
#endif | |
namespace randutils { | |
////////////////////////////////////////////////////////////////////////////// | |
// | |
// seed_seq_fe | |
// | |
////////////////////////////////////////////////////////////////////////////// | |
/* | |
* seed_seq_fe implements a fixed-entropy seed sequence; it conforms to all | |
* the requirements of a Seed Sequence concept. | |
* | |
* seed_seq_fe<N> implements a seed sequence which seeds based on a store of | |
* N * 32 bits of entropy. Typically, it would be initialized with N or more | |
* integers. | |
* | |
* seed_seq_fe128 and seed_seq_fe256 are provided as convenience typedefs for | |
* 128- and 256-bit entropy stores respectively. These variants outperform | |
* std::seed_seq, while being better mixing the bits it is provided as entropy. | |
* In almost all common use cases, they serve as better drop-in replacements | |
* for seed_seq. | |
* | |
* Technical details | |
* | |
* Assuming it constructed with M seed integers as input, it exhibits the | |
* following properties | |
* | |
* * Diffusion/Avalanche: A single-bit change in any of the M inputs has a | |
* 50% chance of flipping every bit in the bitstream produced by generate. | |
* Initializing the N-word entropy store with M words requires O(N * M) | |
* time precisely because of the avalanche requirements. Once constructed, | |
* calls to generate are linear in the number of words generated. | |
* | |
* * Bias freedom/Bijection: If M == N, the state of the entropy store is a | |
* bijection from the M inputs (i.e., no states occur twice, none are | |
* omitted). If M > N the number of times each state can occur is the same | |
* (each state occurs 2**(32*(M-N)) times, where ** is the power function). | |
* If M < N, some states cannot occur (bias) but no state occurs more | |
* than once (it's impossible to avoid bias if M < N; ideally N should not | |
* be chosen so that it is more than M). | |
* | |
* Likewise, the generate function has similar properties (with the entropy | |
* store as the input data). If more outputs are requested than there is | |
* entropy, some outputs cannot occur. For example, the Mersenne Twister | |
* will request 624 outputs, to initialize it's 19937-bit state, which is | |
* much larger than a 128-bit or 256-bit entropy pool. But in practice, | |
* limiting the Mersenne Twister to 2**128 possible initializations gives | |
* us enough initializations to give a unique initialization to trillions | |
* of computers for billions of years. If you really have 624 words of | |
* *real* high-quality entropy you want to use, you probably don't need | |
* an entropy mixer like this class at all. But if you *really* want to, | |
* nothing is stopping you from creating a randutils::seed_seq_fe<624>. | |
* | |
* * As a consequence of the above properties, if all parts of the provided | |
* seed data are kept constant except one, and the remaining part is varied | |
* through K different states, K different output sequences will be produced. | |
* | |
* * Also, because the amount of entropy stored is fixed, this class never | |
* performs dynamic allocation and is free of the possibility of generating | |
* an exception. | |
* | |
* Ideas used to implement this code include hashing, a simple PCG generator | |
* based on an MCG base with an XorShift output function and permutation | |
* functions on tuples. | |
* | |
* More detail at | |
* http://www.pcg-random.org/posts/developing-a-seed_seq-alternative.html | |
*/ | |
template <size_t count = 4, typename IntRep = uint32_t, | |
size_t mix_rounds = 1 + (count <= 2)> | |
struct seed_seq_fe { | |
public: | |
// types | |
typedef IntRep result_type; | |
private: | |
static constexpr uint32_t INIT_A = 0x43b0d7e5; | |
static constexpr uint32_t MULT_A = 0x931e8875; | |
static constexpr uint32_t INIT_B = 0x8b51f9dd; | |
static constexpr uint32_t MULT_B = 0x58f38ded; | |
static constexpr uint32_t MIX_MULT_L = 0xca01f9dd; | |
static constexpr uint32_t MIX_MULT_R = 0x4973f715; | |
static constexpr uint32_t XSHIFT = sizeof(IntRep)*8/2; | |
RANDUTILS_GENERALIZED_CONSTEXPR | |
static IntRep fast_exp(IntRep x, IntRep power) | |
{ | |
IntRep result = IntRep(1); | |
IntRep multiplier = x; | |
while (power != IntRep(0)) { | |
IntRep thismult = power & IntRep(1) ? multiplier : IntRep(1); | |
result *= thismult; | |
power >>= 1; | |
multiplier *= multiplier; | |
} | |
return result; | |
} | |
std::array<IntRep, count> mixer_; | |
template <typename InputIter> | |
void mix_entropy(InputIter begin, InputIter end); | |
public: | |
seed_seq_fe(const seed_seq_fe&) = delete; | |
void operator=(const seed_seq_fe&) = delete; | |
template <typename T> | |
seed_seq_fe(std::initializer_list<T> init) | |
{ | |
seed(init.begin(), init.end()); | |
} | |
template <typename InputIter> | |
seed_seq_fe(InputIter begin, InputIter end) | |
{ | |
seed(begin, end); | |
} | |
// generating functions | |
template <typename RandomAccessIterator> | |
void generate(RandomAccessIterator first, RandomAccessIterator last) const; | |
static constexpr size_t size() | |
{ | |
return count; | |
} | |
template <typename OutputIterator> | |
void param(OutputIterator dest) const; | |
template <typename InputIter> | |
void seed(InputIter begin, InputIter end) | |
{ | |
mix_entropy(begin, end); | |
// For very small sizes, we do some additional mixing. For normal | |
// sizes, this loop never performs any iterations. | |
for (size_t i = 1; i < mix_rounds; ++i) | |
stir(); | |
} | |
seed_seq_fe& stir() | |
{ | |
mix_entropy(mixer_.begin(), mixer_.end()); | |
return *this; | |
} | |
}; | |
template <size_t count, typename IntRep, size_t r> | |
template <typename InputIter> | |
void seed_seq_fe<count, IntRep, r>::mix_entropy(InputIter begin, InputIter end) | |
{ | |
auto hash_const = INIT_A; | |
auto hash = [&](IntRep value) { | |
value ^= hash_const; | |
hash_const *= MULT_A; | |
value *= hash_const; | |
value ^= value >> XSHIFT; | |
return value; | |
}; | |
auto mix = [](IntRep x, IntRep y) { | |
IntRep result = MIX_MULT_L*x - MIX_MULT_R*y; | |
result ^= result >> XSHIFT; | |
return result; | |
}; | |
InputIter current = begin; | |
for (auto& elem : mixer_) { | |
if (current != end) | |
elem = hash(*current++); | |
else | |
elem = hash(0U); | |
} | |
for (auto& src : mixer_) | |
for (auto& dest : mixer_) | |
if (&src != &dest) | |
dest = mix(dest,hash(src)); | |
for (; current != end; ++current) | |
for (auto& dest : mixer_) | |
dest = mix(dest,hash(*current)); | |
} | |
template <size_t count, typename IntRep, size_t mix_rounds> | |
template <typename OutputIterator> | |
void seed_seq_fe<count,IntRep,mix_rounds>::param(OutputIterator dest) const | |
{ | |
const IntRep INV_A = fast_exp(MULT_A, IntRep(-1)); | |
const IntRep MIX_INV_L = fast_exp(MIX_MULT_L, IntRep(-1)); | |
auto mixer_copy = mixer_; | |
for (size_t round = 0; round < mix_rounds; ++round) { | |
// Advance to the final value. We'll backtrack from that. | |
auto hash_const = INIT_A*fast_exp(MULT_A, IntRep(count * count)); | |
for (auto src = mixer_copy.rbegin(); src != mixer_copy.rend(); ++src) | |
for (auto dest = mixer_copy.rbegin(); dest != mixer_copy.rend(); | |
++dest) | |
if (src != dest) { | |
IntRep revhashed = *src; | |
auto mult_const = hash_const; | |
hash_const *= INV_A; | |
revhashed ^= hash_const; | |
revhashed *= mult_const; | |
revhashed ^= revhashed >> XSHIFT; | |
IntRep unmixed = *dest; | |
unmixed ^= unmixed >> XSHIFT; | |
unmixed += MIX_MULT_R*revhashed; | |
unmixed *= MIX_INV_L; | |
*dest = unmixed; | |
} | |
for (auto i = mixer_copy.rbegin(); i != mixer_copy.rend(); ++i) { | |
IntRep unhashed = *i; | |
unhashed ^= unhashed >> XSHIFT; | |
unhashed *= fast_exp(hash_const, IntRep(-1)); | |
hash_const *= INV_A; | |
unhashed ^= hash_const; | |
*i = unhashed; | |
} | |
} | |
std::copy(mixer_copy.begin(), mixer_copy.end(), dest); | |
} | |
template <size_t count, typename IntRep, size_t mix_rounds> | |
template <typename RandomAccessIterator> | |
void seed_seq_fe<count,IntRep,mix_rounds>::generate( | |
RandomAccessIterator dest_begin, | |
RandomAccessIterator dest_end) const | |
{ | |
auto src_begin = mixer_.begin(); | |
auto src_end = mixer_.end(); | |
auto src = src_begin; | |
auto hash_const = INIT_B; | |
for (auto dest = dest_begin; dest != dest_end; ++dest) { | |
auto dataval = *src; | |
if (++src == src_end) | |
src = src_begin; | |
dataval ^= hash_const; | |
hash_const *= MULT_B; | |
dataval *= hash_const; | |
dataval ^= dataval >> XSHIFT; | |
*dest = dataval; | |
} | |
} | |
using seed_seq_fe128 = seed_seq_fe<4, uint32_t>; | |
using seed_seq_fe256 = seed_seq_fe<8, uint32_t>; | |
////////////////////////////////////////////////////////////////////////////// | |
// | |
// auto_seeded | |
// | |
////////////////////////////////////////////////////////////////////////////// | |
/* | |
* randutils::auto_seeded | |
* | |
* Extends a seed sequence class with a nondeterministic default constructor. | |
* Uses a variety of local sources of entropy to portably initialize any | |
* seed sequence to a good default state. | |
* | |
* In normal use, it's accessed via one of the following type aliases, which | |
* use seed_seq_fe128 and seed_seq_fe256 above. | |
* | |
* randutils::auto_seed_128 | |
* randutils::auto_seed_256 | |
* | |
* It's discussed in detail at | |
* http://www.pcg-random.org/posts/simple-portable-cpp-seed-entropy.html | |
* and its motivation (why you can't just use std::random_device) here | |
* http://www.pcg-random.org/posts/cpps-random_device.html | |
*/ | |
template <typename SeedSeq> | |
class auto_seeded : public SeedSeq { | |
using default_seeds = std::array<uint32_t, 13>; | |
template <typename T> | |
static uint32_t crushto32(T value) | |
{ | |
if (sizeof(T) <= 4) | |
return uint32_t(value); | |
else { | |
uint64_t result = uint64_t(value); | |
result *= 0xbc2ad017d719504d; | |
return uint32_t(result ^ (result >> 32)); | |
} | |
} | |
template <typename T> | |
static uint32_t hash(T&& value) | |
{ | |
return crushto32(std::hash<typename std::remove_reference< | |
typename std::remove_cv<T>::type>::type>{}( | |
std::forward<T>(value))); | |
} | |
static constexpr uint32_t fnv(uint32_t hash, const char* pos) | |
{ | |
return *pos == '\0' ? hash : fnv((hash * 16777619U) ^ *pos, pos+1); | |
} | |
default_seeds local_entropy() | |
{ | |
// This is a constant that changes every time we compile the code | |
constexpr uint32_t compile_stamp = | |
fnv(2166136261U, __DATE__ __TIME__ __FILE__); | |
// Some people think you shouldn't use the random device much because | |
// on some platforms it could be expensive to call or "use up" vital | |
// system-wide entropy, so we just call it once. | |
static uint32_t random_int = std::random_device{}(); | |
// The heap can vary from run to run as well. | |
void* malloc_addr = malloc(sizeof(int)); | |
free(malloc_addr); | |
auto heap = hash(malloc_addr); | |
auto stack = hash(&malloc_addr); | |
// Every call, we increment our random int. We don't care about race | |
// conditons. The more, the merrier. | |
random_int += 0xedf19156; | |
// Classic seed, the time. It ought to change, especially since | |
// this is (hopefully) nanosecond resolution time. | |
auto hitime = std::chrono::high_resolution_clock::now() | |
.time_since_epoch().count(); | |
// Address of the thing being initialized. That can mean that | |
// different seed sequences in different places in memory will be | |
// different. Even for the same object, it may vary from run to | |
// run in systems with ASLR, such as OS X, but on Linux it might not | |
// unless we compile with -fPIC -pic. | |
auto self_data = hash(this); | |
// The address of the time function. It should hopefully be in | |
// a system library that hopefully isn't always in the same place | |
// (might not change until system is rebooted though) | |
auto time_func = hash(&std::chrono::high_resolution_clock::now); | |
// The address of the exit function. It should hopefully be in | |
// a system library that hopefully isn't always in the same place | |
// (might not change until system is rebooted though). Hopefully | |
// it's in a different library from time_func. | |
auto exit_func = hash(&_Exit); | |
// The address of a local function. That may be in a totally | |
// different part of memory. On OS X it'll vary from run to run thanks | |
// to ASLR, on Linux it might not unless we compile with -fPIC -pic. | |
// Need the cast because it's an overloaded | |
// function and we need to pick the right one. | |
auto self_func = hash( | |
static_cast<uint32_t (*)(uint64_t)>( | |
&auto_seeded::crushto32)); | |
// Hash our thread id. It seems to vary from run to run on OS X, not | |
// so much on Linux. | |
auto thread_id = hash(std::this_thread::get_id()); | |
// Hash of the ID of a type. May or may not vary, depending on | |
// implementation. | |
#if __cpp_rtti || __GXX_RTTI | |
auto type_id = crushto32(typeid(*this).hash_code()); | |
#else | |
uint32_t type_id = 0; | |
#endif | |
// Platform-specific entropy | |
auto pid = crushto32(RANDUTILS_GETPID); | |
auto cpu = crushto32(RANDUTILS_CPU_ENTROPY); | |
return {{random_int, crushto32(hitime), stack, heap, self_data, | |
self_func, exit_func, time_func, thread_id, type_id, pid, | |
cpu, compile_stamp}}; | |
} | |
public: | |
using SeedSeq::SeedSeq; | |
using base_seed_seq = SeedSeq; | |
const base_seed_seq& base() const | |
{ | |
return *this; | |
} | |
base_seed_seq& base() | |
{ | |
return *this; | |
} | |
auto_seeded(default_seeds seeds) | |
: SeedSeq(seeds.begin(), seeds.end()) | |
{ | |
// Nothing else to do | |
} | |
auto_seeded() | |
: auto_seeded(local_entropy()) | |
{ | |
// Nothing else to do | |
} | |
}; | |
using auto_seed_128 = auto_seeded<seed_seq_fe128>; | |
using auto_seed_256 = auto_seeded<seed_seq_fe256>; | |
////////////////////////////////////////////////////////////////////////////// | |
// | |
// uniform_distribution | |
// | |
////////////////////////////////////////////////////////////////////////////// | |
/* | |
* This template typedef provides either | |
* - uniform_int_distribution, or | |
* - uniform_real_distribution | |
* depending on the provided type | |
*/ | |
template <typename Numeric> | |
using uniform_distribution = typename std::conditional< | |
std::is_integral<Numeric>::value, | |
std::uniform_int_distribution<Numeric>, | |
std::uniform_real_distribution<Numeric> >::type; | |
////////////////////////////////////////////////////////////////////////////// | |
// | |
// random_generator | |
// | |
////////////////////////////////////////////////////////////////////////////// | |
/* | |
* randutils::random_generator | |
* | |
* An Easy-to-Use Random API | |
* | |
* Provides all the power of C++11's random number facility in an easy-to | |
* use wrapper. | |
* | |
* In normal use, it's accessed via one of the following type aliases, which | |
* also use auto_seed_256 by default | |
* | |
* randutils::default_rng | |
* randutils::mt19937_rng | |
* | |
* It's discussed in detail at | |
* http://www.pcg-random.org/posts/ease-of-use-without-loss-of-power.html | |
*/ | |
template <typename RandomEngine = std::default_random_engine, | |
typename DefaultSeedSeq = auto_seed_256> | |
class random_generator { | |
public: | |
using engine_type = RandomEngine; | |
using default_seed_type = DefaultSeedSeq; | |
private: | |
engine_type engine_; | |
// This SFNAE evilness provides a mechanism to cast classes that aren't | |
// themselves (technically) Seed Sequences but derive from a seed | |
// sequence to be passed to functions that require actual Seed Squences. | |
// To do so, the class should provide a the type base_seed_seq and a | |
// base() member function. | |
template <typename T> | |
static constexpr bool has_base_seed_seq(typename T::base_seed_seq*) | |
{ | |
return true; | |
} | |
template <typename T> | |
static constexpr bool has_base_seed_seq(...) | |
{ | |
return false; | |
} | |
template <typename SeedSeqBased> | |
static auto seed_seq_cast(SeedSeqBased&& seq, | |
typename std::enable_if< | |
has_base_seed_seq<SeedSeqBased>(0)>::type* = 0) | |
-> decltype(seq.base()) | |
{ | |
return seq.base(); | |
} | |
template <typename SeedSeq> | |
static SeedSeq seed_seq_cast(SeedSeq&& seq, | |
typename std::enable_if< | |
!has_base_seed_seq<SeedSeq>(0)>::type* = 0) | |
{ | |
return seq; | |
} | |
public: | |
template <typename Seeding = default_seed_type, | |
typename... Params> | |
random_generator(Seeding&& seeding = default_seed_type{}) | |
: engine_{seed_seq_cast(std::forward<Seeding>(seeding))} | |
{ | |
// Nothing (else) to do | |
} | |
// Work around Clang DR777 bug in Clang 3.6 and earlier by adding a | |
// redundant overload rather than mixing parameter packs and default | |
// arguments. | |
// https://llvm.org/bugs/show_bug.cgi?id=23029 | |
template <typename Seeding, | |
typename... Params> | |
random_generator(Seeding&& seeding, Params&&... params) | |
: engine_{seed_seq_cast(std::forward<Seeding>(seeding)), | |
std::forward<Params>(params)...} | |
{ | |
// Nothing (else) to do | |
} | |
template <typename Seeding = default_seed_type, | |
typename... Params> | |
void seed(Seeding&& seeding = default_seed_type{}) | |
{ | |
engine_.seed(seed_seq_cast(seeding)); | |
} | |
// Work around Clang DR777 bug in Clang 3.6 and earlier by adding a | |
// redundant overload rather than mixing parameter packs and default | |
// arguments. | |
// https://llvm.org/bugs/show_bug.cgi?id=23029 | |
template <typename Seeding, | |
typename... Params> | |
void seed(Seeding&& seeding, Params&&... params) | |
{ | |
engine_.seed(seed_seq_cast(seeding), std::forward<Params>(params)...); | |
} | |
RandomEngine& engine() | |
{ | |
return engine_; | |
} | |
template <typename ResultType, | |
template <typename> class DistTmpl = std::normal_distribution, | |
typename... Params> | |
ResultType variate(Params&&... params) | |
{ | |
DistTmpl<ResultType> dist(std::forward<Params>(params)...); | |
return dist(engine_); | |
} | |
template <typename Numeric> | |
Numeric uniform(Numeric lower, Numeric upper) | |
{ | |
return variate<Numeric,uniform_distribution>(lower, upper); | |
} | |
template <template <typename> class DistTmpl = uniform_distribution, | |
typename Iter, | |
typename... Params> | |
void generate(Iter first, Iter last, Params&&... params) | |
{ | |
using result_type = | |
typename std::remove_reference<decltype(*(first))>::type; | |
DistTmpl<result_type> dist(std::forward<Params>(params)...); | |
std::generate(first, last, [&]{ return dist(engine_); }); | |
} | |
template <template <typename> class DistTmpl = uniform_distribution, | |
typename Range, | |
typename... Params> | |
void generate(Range&& range, Params&&... params) | |
{ | |
generate<DistTmpl>(std::begin(range), std::end(range), | |
std::forward<Params>(params)...); | |
} | |
template <typename Iter> | |
void shuffle(Iter first, Iter last) | |
{ | |
std::shuffle(first, last, engine_); | |
} | |
template <typename Range> | |
void shuffle(Range&& range) | |
{ | |
shuffle(std::begin(range), std::end(range)); | |
} | |
template <typename Iter> | |
Iter choose(Iter first, Iter last) | |
{ | |
auto dist = std::distance(first, last); | |
if (dist < 2) | |
return first; | |
using distance_type = decltype(dist); | |
distance_type choice = uniform(distance_type(0), --dist); | |
std::advance(first, choice); | |
return first; | |
} | |
template <typename Range> | |
auto choose(Range&& range) -> decltype(std::begin(range)) | |
{ | |
return choose(std::begin(range), std::end(range)); | |
} | |
template <typename Range> | |
auto pick(Range&& range) -> decltype(*std::begin(range)) | |
{ | |
return *choose(std::begin(range), std::end(range)); | |
} | |
template <typename T> | |
auto pick(std::initializer_list<T> range) -> decltype(*range.begin()) | |
{ | |
return *choose(range.begin(), range.end()); | |
} | |
template <typename Size, typename Iter> | |
Iter sample(Size to_go, Iter first, Iter last) | |
{ | |
auto total = std::distance(first, last); | |
using value_type = decltype(*first); | |
return std::stable_partition(first, last, | |
[&](const value_type&) { | |
--total; | |
using distance_type = decltype(total); | |
distance_type zero{}; | |
if (uniform(zero, total) < to_go) { | |
--to_go; | |
return true; | |
} else { | |
return false; | |
} | |
}); | |
} | |
template <typename Size, typename Range> | |
auto sample(Size to_go, Range&& range) -> decltype(std::begin(range)) | |
{ | |
return sample(to_go, std::begin(range), std::end(range)); | |
} | |
}; | |
using default_rng = random_generator<std::default_random_engine>; | |
using mt19937_rng = random_generator<std::mt19937>; | |
} | |
#endif // RANDUTILS_HPP |
The update to auto_seeded::hash()
is backwards – std::hash<typename std::remove_reference<typename std::remove_cv<T>::type>::type>
should be std::hash<typename std::remove_cv<typename std::remove_reference<T>::type>::type>
(or possibly just std::hash<typename std::decay<T>::type>
).
Hi, thank you for this code usefull and really easy to use 👍 and also for all your blog posts dealing with PRNG.
I use it now as a base for my samplers in a Monte Carlo path tracer.
But i can't figure out how to properly resolve the following warning given by g++ 7.3.0 :
Edit: I compiled it with clang++ 6.0.0, and got the same warning.
randutils.hpp:441:21: warning: mangled name for ‘static uint32_t randutils::auto_seeded<SeedSeq>::hash(T&&) [with T = void (*)(int) throw (); SeedSeq = randutils::seed_seq_fe<8, unsigned int>]’ will change in C++17 because the exception specification is part of a function type [-Wnoexcept-type] static uint32_t hash(T&& value)
It seems that a keyword like "noexecpt" is missing somewhere, but i don't manage to find the answer. My lack of knowledge with templates and c++17 is definitely the reason. If you have any idea i would be glad to know.
Cheers.
@bfraboni Same here, but it compiles cleanly on GCC 8.3.0. If you can't upgrade, you can compile with -Wno-noexcept-type
to disable the warning, which is fine unless you expose this API in a shared library.
Thank you for the code and articles. My compiler complains about the variables compile_stamp and time_func not being used so I made a revision.
The platform-specific logic for auto_seeded
leads to a runtime crash on certain aarch64 architectures. The relevant code is here:
// Ugly platform-specific code for auto_seeded
#if !defined(RANDUTILS_CPU_ENTROPY) && defined(__has_builtin)
#if __has_builtin(__builtin_readcyclecounter)
#define RANDUTILS_CPU_ENTROPY __builtin_readcyclecounter()
#endif
#endif
__has_builtin(__builtin_readcyclecounter)
returns true and the compiler exits successfully, but the required instruction is not available in userspace (without a patched kernel) and results in an illegal hardware instruction when run.
Discussion upstream: https://bugs.llvm.org/show_bug.cgi?id=44307
Hey!
Can anyone explain to me the significance of
using default_seeds = std::array<uint32_t, 11>;
What's the deal with 11? Is it just some number deemed big enough for seed data, but not the minimum?
Related: Is it a good idea to provide 11 unit32 or is a smaller number (like 8 for auto_seeded_256 and 4 for auto_seeded_128) always sufficient?
Oh, I see. Thats just the number, the default constructor creates and uses.
Nevertheless, when I seed, what is an appropriate number of arguments?
The platform-specific logic for
auto_seeded
leads to a runtime crash on certain aarch64 architectures.
Fixed by explicitly avoiding __builtin_readcyclecounter
on aarch64.
I've also fixed an issue where it didn't use two pieces of local entropy it generated, which could trigger unused-variable warnings. This increases the initial seed entropy to 13 32-bit words.
I notice several workarounds for llvm.23029 in line 662 and 682 of randutils.hpp :
// Work around Clang DR777 bug in Clang 3.6 and earlier by adding a
// redundant overload rather than mixing parameter packs and default
// arguments.
// https://llvm.org/bugs/show_bug.cgi?id=23029
template <typename Seeding,
typename... Params>
random_generator(Seeding&& seeding, Params&&... params)
: engine_{seed_seq_cast(std::forward<Seeding>(seeding)),
std::forward<Params>(params)...}
{
// Nothing (else) to do
}
This compiler bug is already fixed:https://bugs.llvm.org/show_bug.cgi?id=23029
Shall these workarounds be removed?Is the compiler bug actually fixed?
choose() traverses the collection too much if the iterators are not random access.