HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Better rand() API

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
randapibetter

Problem

I use this document as a basis for my mini-library:

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 a
global 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 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.