33

C++11 introduced the header <random> with declarations for random number engines and random distributions. That's great - time to replace those uses of rand() which is often problematic in various ways. However, it seems far from obvious how to replace

srand(n);
// ...
int r = rand();

Based on the declarations it seems a uniform distribution can be built something like this:

std::default_random_engine engine;
engine.seed(n);
std::uniform_int_distribution<> distribution;
auto rand = [&](){ return distribution(engine); }

This approach seems rather involved and is surely something I won't remember unlike the use of srand() and rand(). I'm aware of N4531 but even that still seems to be quite involved.

Is there a reasonably simple way to replace srand() and rand()?

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • 4
    I think the best way is to build yourself some functions that expose this functionality and use them whenever you need. – Marius Bancila Oct 03 '15 at 21:38
  • 7
    sidenote: Don't use `default_random_engine`, it's often as low-quality as `rand()`. Use `random_device` and/or `std::mt19937`. – The Paramagnetic Croissant Oct 03 '15 at 21:52
  • 4
    @TheParamagneticCroissant: if that is, indeed, the case it seems ill-advised to call it the `default_random_engine`. I guess, I should file a defect to clarify, at least, that the default random engine should, by default, not be used... – Dietmar Kühl Oct 03 '15 at 21:55
  • @DietmarKühl yes, it's an unfortunate naming… [here's a related Q/A](https://stackoverflow.com/questions/30240899/should-i-use-stddefault-random-engine-or-should-i-use-stdmt19937). – The Paramagnetic Croissant Oct 03 '15 at 22:01
  • 4
    I think it would help to explore the original use case a bit further. I imagine that in most cases you won't just use `r`, but apply some further (buggy?) transformations to get from X to Y. While `rand()` may solve X, perhaps it's not so important to replace `rand()`, but rather show how `` solves Y directly. – Kerrek SB Oct 03 '15 at 22:27
  • @KerrekSB: actually, my current use case most likely would be fine with `rand()`: I'm creating random inputs to benchmark different implementations of `std::sort()` using something along the lines of `std::vector v(size); std::generate(v.begin(), v.end(), std::rand);`. The word is that I shall not use `std::rand` so I'm wondering how to replace it. That said, useful inputs to benchmark sort algorithms _would_ be appreciated :-) – Dietmar Kühl Oct 03 '15 at 22:31
  • 1
    Distribution objects should be cheap; you could use `std::uniform_int_distribution<>()(engine);` instead of using a global one, which also allows you to easily use a different distribution at every call site. – user253751 Oct 04 '15 at 01:13
  • 1
    Dietmar, see section 4 in http://open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3847.pdf and if you need thread-safety add `thread_local` to the static variables to get per-thread RNGs. – Jonathan Wakely Oct 04 '15 at 13:30

5 Answers5

20

Is there a reasonably simple way to replace srand() and rand()?

Full disclosure: I don't like rand(). It's bad, and it's very easily abused.

The C++11 random library fills in a void that has been lacking for a long, long time. The problem with high quality random libraries is that they're oftentimes hard to use. The C++11 <random> library represents a huge step forward in this regard. A few lines of code and I have a very nice generator that behaves very nicely and that easily generates random variates from many different distributions.


Given the above, my answer to you is a bit heretical. If rand() is good enough for your needs, use it. As bad as rand() is (and it is bad), removing it would represent a huge break with the C language. Just make sure that the badness of rand() truly is good enough for your needs.

C++14 didn't deprecate rand(); it only deprecated functions in the C++ library that use rand(). While C++17 might deprecate rand(), it won't delete it. That means you have several more years before rand() disappears. The odds are high that you will have retired or switched to a different language by the time the C++ committee finally does delete rand() from the C++ standard library.

I'm creating random inputs to benchmark different implementations of std::sort() using something along the lines of std::vector<int> v(size); std::generate(v.begin(), v.end(), std::rand);

You don't need a cryptographically secure PRNG for that. You don't even need Mersenne Twister. In this particular case, rand() probably is good enough for your needs.


Update
There is a nice simple replacement for rand() and srand() in the C++11 random library: std::minstd_rand.

#include <random>
#include <iostream>

int main ()
{
    std:: minstd_rand simple_rand;

    // Use simple_rand.seed() instead of srand():
    simple_rand.seed(42);

    // Use simple_rand() instead of rand():
    for (int ii = 0; ii < 10; ++ii)
    {
        std::cout << simple_rand() << '\n';
    }
}

The function std::minstd_rand::operator()() returns a std::uint_fast32_t. However, the algorithm restricts the result to between 1 and 231-2, inclusive. This means the result will always convert safely to a std::int_fast32_t (or to an int if int is at least 32 bits long).

David Hammen
  • 32,454
  • 9
  • 60
  • 108
  • Excellent - I entirely missed this easy to use approach! Thank you very much! – Dietmar Kühl Oct 04 '15 at 16:05
  • @DietmarKühl - My update as originally typed wasn't correct with regard to seeding. I forgot to call `seed`! The fix is simple: use `simple_rand.seed(some_seed_value)`. – David Hammen Oct 04 '15 at 16:48
  • Yes, but the error was easy to fix. Also, it seems using `std::minstd_rand simple_rand(n)` would combine seeding and creation getting quite close to what I was hoping for. – Dietmar Kühl Oct 04 '15 at 16:53
6

How about randutils by Melissa O'Neill of pcg-random.org?

From the introductory blog post:

randutils::mt19937_rng rng;

std::cout << "Greetings from Office #" << rng.uniform(1,17)
          << " (where we think PI = "  << rng.uniform(3.1,3.2) << ")\n\n"
          << "Our office morale is "   << rng.uniform('A','D') << " grade\n";
jtbandes
  • 115,675
  • 35
  • 233
  • 266
  • I haven't thoroughly read the post but it seems my use of `rand()` would be replace by `randutils::mt19937_rng rng; rng.uniform(0, std::numeric_limits::max());`. That seems to be similarly memorable as the use mentioned in the question. – Dietmar Kühl Oct 03 '15 at 21:46
  • Nice toolkit. `` is powerful yet "low-level": we'd need some functions built upon it to abstract the most common needs. – edmz Oct 04 '15 at 13:29
6

Assuming you want the behavior of the C-style rand and srand functions, including their quirkiness, but with good random, this is the closest I could get.

#include <random>
#include <cstdlib>  // RAND_MAX  (might be removed soon?)
#include <climits>  // INT_MAX   (use as replacement?)


namespace replacement
{

  constexpr int rand_max {
#ifdef RAND_MAX
      RAND_MAX
#else
      INT_MAX
#endif
  };

  namespace detail
  {

    inline std::default_random_engine&
    get_engine() noexcept
    {
      // Seeding with 1 is silly, but required behavior
      static thread_local auto rndeng = std::default_random_engine(1);
      return rndeng;
    }

    inline std::uniform_int_distribution<int>&
    get_distribution() noexcept
    {
      static thread_local auto rnddst = std::uniform_int_distribution<int> {0, rand_max};
      return rnddst;
    }

  }  // namespace detail

  inline int
  rand() noexcept
  {
    return detail::get_distribution()(detail::get_engine());
  }

  inline void
  srand(const unsigned seed) noexcept
  {
    detail::get_engine().seed(seed);
    detail::get_distribution().reset();
  }

  inline void
  srand()
  {
    std::random_device rnddev {};
    srand(rnddev());
  }

}  // namespace replacement

The replacement::* functions can be used exactly like their std::* counterparts from <cstdlib>. I have added a srand overload that takes no arguments and seeds the engine with a “real” random number obtained from a std::random_device. How “real” that randomness will be is of course implementation defined.

The engine and the distribution are held as thread_local static instances so they carry state across multiple calls but still allow different threads to observe predictable sequences. (It's also a performance gain because you don't need to re-construct the engine or use locks and potentially trash other people's cashes.)

I've used std::default_random_engine because you did but I don't like it very much. The Mersenne Twister engines (std::mt19937 and std::mt19937_64) produce much better “randomness” and, surprisingly, have also been observed to be faster. I don't think that any compliant program must rely on std::rand being implemented using any specific kind of pseudo random engine. (And even if it did, implementations are free to define std::default_random_engine to whatever they like so you'd have to use something like std::minstd_rand to be sure.)

5gon12eder
  • 24,280
  • 5
  • 45
  • 92
  • 2
    While this code is impressive and IMO well designed, I do not think that this is a "reasonably simple way to replace srand() and rand()"... –  Oct 03 '15 at 22:33
  • 1
    @GuillaumeLethuillier: Well that's C++'s fault innit. – Lightness Races in Orbit Oct 03 '15 at 22:42
  • @GuillaumeLethuillier You can put these 50 lines of code into a small header file and then call `replacement::rand()` just like `std::rand()` as many times as you want in any place of your project. I don't think that this is an impractical thing to do if you really like and want to keep using the interface `rand` and `srand` provide. It might not pay off for a very tiny project (like in “hello world”), though. – 5gon12eder Oct 04 '15 at 01:44
  • 1
    What's the purpose of the lambda, why can't you just construct the engine with the seed? [rand.req.eng] says `E e{}; e.seed(s);` is equivalent to `E e{s};` – Jonathan Wakely Oct 04 '15 at 13:39
  • @JonathanWakely You are right. It's a habit I've picked on to initialize the engine with a lambda because normally I'll want to seed it with a more complicated *random* seed. In this case, it is ridiculous. – 5gon12eder Oct 04 '15 at 17:03
  • OK, it's better without the lambda, but I still don't understand the "silly, but required" comment. If you don't want to seed it explicitly just default-construct it, which uses a default seed, which is no worse than using a hardcoded seed of 1. – Jonathan Wakely Oct 04 '15 at 17:15
  • @JonathanWakely I'm seeding with 1 because if you use `std::rand()` without prior seeding, the standard requires it to produce a sequence as if `std::srand(1)` was called before and I wanted to match that behavior because I thought the OP asked for that. – 5gon12eder Oct 05 '15 at 18:04
  • Unless you happen to know that `std::default_random_engine` uses exactly the same PRNG as `std::rand()` that seems a bit pointless, as using the same initial seed won't produce the same behaviour otherwise. – Jonathan Wakely Oct 05 '15 at 22:47
  • @JonathanWakely I don't think so. Consider `const auto x1 = std::rand();` assuming that nothing else in your program called `std::rand` or `std::srand` so far. Then you have no guarantees about what the value of `x1` might be. However, you *can* rely on that if you now do `std::srand(1); const auto x2 = std::rand();` then `assert(x1 == x2);` will hold. It's this behavior that I wanted to match – as poor a design choice I think it is because I believe that most programs would benefit from `std::rand` *not* returning the same sequence every time unless explicitly seeded with real random. – 5gon12eder Oct 05 '15 at 22:57
  • But the `1` is unnecessary, a default constructed engine guarantees to have the same initial state as any other default-constructed object of the same type: _"Creates an engine with the same initial state as all other default-constructed engines of type E."_ So why use 1, not 999, or 1234, or just default-construct it with the default seed? My point is that your "silly, but required" initial seed of 1 is completely arbitrary and not required at all. The only way that the value of 1 would be useful is if `default_random_engine` uses the same PRNG as `std::rand()` but a different initial state. – Jonathan Wakely Oct 06 '15 at 00:16
  • @JonathanWakely I'm afraid I'll start repeating myself but how would you meet the [requirement](http://en.cppreference.com/w/cpp/numeric/random/rand) that *“if `rand()` is used before any calls to `srand()`, `rand()` behaves as if it was seeded with `srand(1)`”* without explicitly seeding the engine with 1? I don't see how the algorithm used by the PRNG is relevant here, as long as it is deterministic. I'll be happy to continue this discussion in a chat if you have interest. Maybe this would be easier. – 5gon12eder Oct 06 '15 at 02:52
  • Note that my goal is *not* to achieve that `std::rand() == replacement::rand()`. This would indeed require knowledge about `std::rand`'s PRNG which isn't specified by the standard. – 5gon12eder Oct 06 '15 at 02:56
  • Yes, we're both repeating ourselves, let's [take it to chat...](http://chat.stackoverflow.com/rooms/91471/how-to-use-random-to-replace-rand) – Jonathan Wakely Oct 06 '15 at 10:32
4

Abusing the fact that engines return values directly

All engines defined in <random> has an operator()() that can be used to retrieve the next generated value, as well as advancing the internal state of the engine.

std::mt19937 rand (seed); // or an engine of your choosing
for (int i = 0; i < 10; ++i) {
  unsigned int x = rand ();
  std::cout << x << std::endl;
}

It shall however be noted that all engines return a value of some unsigned integral type, meaning that they can potentially overflow a signed integral (which will then lead to undefined-behavior).

If you are fine with using unsigned values everywhere you retrieve a new value, the above is an easy way to replace usage of std::srand + std::rand.

Note: Using what has been described above might lead to some values having a higher chance of being returned than others, due to the fact that the result_type of the engine not having a max value that is an even multiple of the highest value that can be stored in the destination type.
If you have not worried about this in the past — when using something like rand()%low+high — you should not worry about it now.

Note: You will need to make sure that the std::engine-type::result_type is at least as large as your desired range of values (std::mt19937::result_type is uint_fast32_t).


If you only need to seed the engine once

There is no need to first default-construct a std::default_random_engine (which is just a typedef for some engine chosen by the implementation), and later assigning a seed to it; this could be done all at once by using the appropriate constructor of the random-engine.

std::random-engine-type engine (seed);

If you however need to re-seed the engine, using std::random-engine::seed is the way to do it.


If all else fails; create a helper-function

Even if the code you have posted looks slightly complicated, you are only meant to write it once.

If you find yourself in a situation where you are tempted to just copy+paste what you have written to several places in your code it is recommended, as always when doing copy+pasting; introduce a helper-function.

Intentionally left blank, see other posts for example implementations.
Community
  • 1
  • 1
Filip Roséen - refp
  • 62,493
  • 20
  • 150
  • 196
  • 1
    Using a random number engine directly, rather than through a distribution, is an anti-pattern and is indeed abusing them, and not recommended even when you are happy to get the entire min-max range, see 3.3 in http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3847.pdf – Jonathan Wakely Oct 04 '15 at 13:22
  • I whole-heartedly agree with you on that one @JonathanWakely (hence the using of *"abusing"* in the header of that section). It will result in the same (not so pleasant) behavior such as `std::rand () % a + b`, as stated in the answer. – Filip Roséen - refp Oct 04 '15 at 13:24
  • 1
    @JonathanWakely - I think Walter Brown is being overly pedantic in that article. There are lots of valid use cases for good old `std::rand()`. The question at hand is a good example. All Dietmar Kühl needs is something simple that can fill an array with somewhat random data so he can compare sorting algorithms. The data do not have to be perfectly distributed. – David Hammen Oct 04 '15 at 18:27
1

You can create a simple function like this:

#include <random>
#include <iostream>
int modernRand(int n) {
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(0, n);
    return dis(gen);
}

And later use it like this:

int myRandValue = modernRand(n);

As mentioned here

UpmostScarab
  • 960
  • 10
  • 29
  • 1
    The implication with the above code is that creation of generators and distributions is rather cheap. Is that the case? Another implication is that their use is thread-safe and I can't see special guarantees for the random number generators or the distributions that they are thread-safe, i.e., use of this function from different threads introduces a data race. – Dietmar Kühl Oct 03 '15 at 21:53
  • @DietmarKühl Well I actualli can't give you any guarantees at that point, sorry. Haven't tested that class alot by myself. – UpmostScarab Oct 03 '15 at 22:07
  • [Instantiation of distributions is really cheap](http://stackoverflow.com/a/19036349/3425536). No idea whether that's the case with generators, probably not. – Emil Laine Oct 03 '15 at 22:08
  • 7
    This is an *extremely expensive* way to produce a random integer. Creating an instance of `std::random_device` will open a file (and close it in its destructor) on many platforms. On my implementation, `sizeof(std::mt19937) == 5000` and it will shuffle around all those bytes in its warm-up phase which you'll run for each single integer to be generated. – 5gon12eder Oct 03 '15 at 22:14
  • 1
    How about adding static to all of the variable declarations. That initialize them once, and use them repeatedly. It's not thread safe, but neither is rand(). Another option is thread_local, but then you pay every time you use it from a new thread – Dave S Oct 03 '15 at 22:27
  • @DietmarKühl, this function is thread-safe, there is no shared state in any of the objects (except potentially via an external device such as `/dev/random` used by `random_device` but reads from that will not race). No special guarantees are needed: the general rules in [res.on.data.races] apply, accessing distinct objects in separate threads does not race. It is extremely expensive though. – Jonathan Wakely Oct 04 '15 at 13:31
  • @JonathanWakely: yes, of course - the objects are local and not shared. I think I made the transition to avoid the [assumed] creation overhead and thought about making the objects `static` and commented on the thread-safety of that approach. – Dietmar Kühl Oct 04 '15 at 16:01