From 00d8f899d5731e6294f6852b79402c907b03c034 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Thu, 21 Oct 2021 13:12:00 -0400 Subject: [PATCH] Add random::get() functions. --- include/random.hpp | 115 +++++++++++++++++++++++++++++++-------------- src/random.cpp | 44 ++--------------- 2 files changed, 84 insertions(+), 75 deletions(-) diff --git a/include/random.hpp b/include/random.hpp index c97bff59..0869be19 100644 --- a/include/random.hpp +++ b/include/random.hpp @@ -16,7 +16,6 @@ Example: std::random_device rd; random::Xoroshiro128Plus rng(rd(), rd()); uint64_t r = rng(); - uint32_t r = rng.u32(); std::uniform_real_distribution uniform(0.f, 1.f); float r = uniform(rng); @@ -59,31 +58,10 @@ struct Xoroshiro128Plus { constexpr uint64_t max() const { return UINT64_MAX; } - - uint64_t u64() { - return operator()(); - } - uint64_t u32() { - // Take top 32 bits which has better randomness properties. - return u64() >> 32; - } - uint16_t u16() { - return u64() >> 48; - } - uint8_t u8() { - return u64() >> 56; - } - float f32() { - // The multiplier is 2f7fffff in hex. This gives maximum precision of uint32_t -> float conversion and its image is [0, 1). - return u32() * 2.32830629e-10f; - } - float f64() { - return u64() * 5.421010862427522e-20; - } }; -// Simple random API +// Simple thread-local API /** Initializes the thread-local RNG state. Must call when creating a thread, otherwise random state is undefined (might always return 0). @@ -92,26 +70,91 @@ void init(); /** Returns the thread-local generator. */ -Xoroshiro128Plus& get(); +Xoroshiro128Plus& local(); -/** Returns a uniform random uint64_t from 0 to UINT64_MAX */ -inline uint64_t u64() { - return get().u64(); +template +T get() { + // Call operator()() and cast by default + return local()(); } -/** Returns a uniform random uint32_t from 0 to UINT32_MAX */ -inline uint32_t u32() { - return get().u32(); + +template <> +inline uint32_t get() { + // Take top 32 bits which has better randomness properties. + return get() >> 32; } -/** Returns a uniform random float in the interval [0.0, 1.0) */ -inline float uniform() { - return get().f32(); + +template <> +inline uint16_t get() { + return get() >> 48; +} + +template <> +inline uint8_t get() { + return get() >> 56; +} + +template <> +inline bool get() { + return get() >> 63; } + +template <> +inline float get() { + // The multiplier is 2f7fffff in hex. This gives maximum precision of uint32_t -> float conversion and its image is [0, 1). + return get() * 2.32830629e-10f; +} + +template <> +inline double get() { + return get() * 5.421010862427522e-20; +} + + +/** Returns a uniform random uint64_t from 0 to UINT64_MAX */ +inline uint64_t u64() {return get();} +/** Returns a uniform random uint32_t from 0 to UINT32_MAX */ +inline uint32_t u32() {return get();} +/** Returns a uniform random float in the interval [0.0, 1.0) */ +inline float uniform() {return get();} + /** Returns a normal random number with mean 0 and standard deviation 1 */ -float normal(); +inline float normal() { + // Box-Muller transform + float radius = std::sqrt(-2.f * std::log(1.f - get())); + float theta = 2.f * M_PI * get(); + return radius * std::sin(theta); + + // // Central Limit Theorem + // const int n = 8; + // float sum = 0.0; + // for (int i = 0; i < n; i++) { + // sum += get(); + // } + // return (sum - n / 2.f) / std::sqrt(n / 12.f); +} + /** Fills an array with random bytes. */ -void buffer(uint8_t* out, size_t len); +inline void buffer(uint8_t* out, size_t len) { + Xoroshiro128Plus& rng = local(); + for (size_t i = 0; i < len; i += 4) { + uint64_t r = rng(); + out[i] = r; + if (i + 1 < len) + out[i + 1] = r >> 8; + if (i + 2 < len) + out[i + 2] = r >> 16; + if (i + 3 < len) + out[i + 3] = r >> 24; + } +} + /** Creates a vector of random bytes. */ -std::vector vector(size_t len); +inline std::vector vector(size_t len) { + std::vector v(len); + buffer(v.data(), len); + return v; +} } // namespace random diff --git a/src/random.cpp b/src/random.cpp index af2ad35a..86305b4a 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -20,9 +20,12 @@ void init() { if (rng.isSeeded()) return; + // Get epoch time in microseconds for seed struct timeval tv; gettimeofday(&tv, NULL); - rng.seed(uint64_t(tv.tv_sec) * 1000 * 1000 + tv.tv_usec, threadCounter++); + uint64_t usec = uint64_t(tv.tv_sec) * 1000 * 1000 + tv.tv_usec; + // Add number of initialized threads so far to random seed, so two threads don't get the same seed if initialized at the same time. + rng.seed(usec, threadCounter++); // Shift state a few times due to low seed entropy for (int i = 0; i < 4; i++) { rng(); @@ -30,47 +33,10 @@ void init() { } -Xoroshiro128Plus& get() { +Xoroshiro128Plus& local() { return rng; } -float normal() { - // Box-Muller transform - float radius = std::sqrt(-2.f * std::log(1.f - uniform())); - float theta = 2.f * M_PI * uniform(); - return radius * std::sin(theta); - - // // Central Limit Theorem - // const int n = 8; - // float sum = 0.0; - // for (int i = 0; i < n; i++) { - // sum += uniform(); - // } - // return (sum - n / 2.f) / std::sqrt(n / 12.f); -} - - -void buffer(uint8_t* out, size_t len) { - for (size_t i = 0; i < len; i += 4) { - uint64_t r = u64(); - out[i] = r; - if (i + 1 < len) - out[i + 1] = r >> 8; - if (i + 2 < len) - out[i + 2] = r >> 16; - if (i + 3 < len) - out[i + 3] = r >> 24; - } -} - - -std::vector vector(size_t len) { - std::vector v(len); - buffer(v.data(), len); - return v; -} - - } // namespace random } // namespace rack