patterncppMinor
Better rand() API
Viewed 0 times
randapibetter
Problem
I use this document as a basis for my mini-library:
Motivation
The
-
A direct replacement to the
global uniform random number generator.
-
To expose the most widely-used combo in C++11 `
#include
#include
#include
#include
/* rdtsc
*
* The instruction measures the total pseudo-cycles since the processor
* was powered on. Given the high frequency of today's machines, it's
* extremely unlikely that two processors will return the same value
* even if they booted at the same time and are clocked a
Motivation
The
std::rand friends are discouraged in C++14, so we want:-
A direct replacement to the
std::rand friends. Despite of the security issues, std::rand is considered both handy and useful as aglobal uniform random number generator.
-
To expose the most widely-used combo in C++11 `
without pushing the users to learn the whole design. Smoothing the learning
curve can usually optimize the acceptance.that using rdtsc
Design Decisions
std::rand is a self-contained interface, so its replacement should
be independent as well. In addition, I expect the interface to
correctly expose the functionalities of and lead to more
robust and secure programs. The proposed replacement is
-
Distribution based. RNG must be used with a distribution; std::rand is a wrong design.
-
Randomly seeded before used. Improper seeding like rand(time(0)) may result in vulnerabilities.
-
Per-thread engine. Minimal interface should minimize astonishment, [with respect to] thread-safety and performance.
-
Manually seedable. User can observe repeatability in a given thread, which is a typical demand for debugging.
-
Type-safe. No integral promotion, no loss of distribution property during down-casting. For a given invocation, the inputs and the result
have the same type.
Questions:
-
Do I follow the spec well? Primarily worried about 32-bit vs 64-bit issues.
-
How can I do the ugly enable_if better?
-
How can I do the ugly macros better?
-
Good way to seed? Neither rdtsc or std::random_device are very portable.
``#include
#include
#include
#include
/* rdtsc
*
* The instruction measures the total pseudo-cycles since the processor
* was powered on. Given the high frequency of today's machines, it's
* extremely unlikely that two processors will return the same value
* even if they booted at the same time and are clocked a
Solution
It's not clear to me why we need to downgrade from
C++17 was standardised after this code was written; it provides
The implementation here still embeds an assumption that
Here,
With C++20 Concepts, we can replace the
The thread-local engines could be problematic in some applications. It's important that the user be aware that
Given that many calls would like to use the same distribution, it would be nice to have an interface that returns a combination of generator and distribution, that can be used as a source of numbers drawn (with replacement) from a single population. That seems like a useful enhancement.
std::random_device to a timestamp-based seed on Intel platforms. If we have good reason to distrust a particular library's implementation, then submit a patch mixing timestamp into the randomness, rather than ignoring the platform's randomness.C++17 was standardised after this code was written; it provides
if constexpr to simplify the two versions of seed():template
auto seed()
{
std::random_device rd;
if constexpr (std::is_same_v) {
std::uint_fast64_t value = rd();
value = (value << 32) | rd();
return value;
} else {
return rd();
}
}The implementation here still embeds an assumption that
std::random_device::max is 2³². A more portable version adapts to the actual sizes of the random device and engine, perhaps like this:template
auto seed()
{
using result_type = typename Engine::result_type;
std::random_device rd;
result_type result = 0;
auto const step = rd.max() / 2 + 1;
auto const start = std::numeric_limits::max();
for (auto needed = start; needed; needed = needed / 2 / step) {
result = result * 2 * step | rd();
}
return result;
}Here,
needed keeps track of how much more needs to be generated, and step represents how much is added from the device in each iteration (divided by 2, to make it an exact power).With C++20 Concepts, we can replace the
static_assert in randint() with a constraint:template
IntType randint(IntType a, IntType b)
{The thread-local engines could be problematic in some applications. It's important that the user be aware that
reseed() applies only to the calling thread's instance, and does not affect any other.Given that many calls would like to use the same distribution, it would be nice to have an interface that returns a combination of generator and distribution, that can be used as a source of numbers drawn (with replacement) from a single population. That seems like a useful enhancement.
Code Snippets
template <typename Engine = random_engine>
auto seed()
{
std::random_device rd;
if constexpr (std::is_same_v<Engine, std::mt19937_64>) {
std::uint_fast64_t value = rd();
value = (value << 32) | rd();
return value;
} else {
return rd();
}
}template <typename Engine = random_engine>
auto seed()
{
using result_type = typename Engine::result_type;
std::random_device rd;
result_type result = 0;
auto const step = rd.max() / 2 + 1;
auto const start = std::numeric_limits<result_type>::max();
for (auto needed = start; needed; needed = needed / 2 / step) {
result = result * 2 * step | rd();
}
return result;
}template <std::integral IntType>
IntType randint(IntType a, IntType b)
{Context
StackExchange Code Review Q#104103, answer score: 5
Revisions (0)
No revisions yet.