How to use to replace rand()?

2019-03-11 17:19发布

问题:

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()?

回答1:

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).



回答2:

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";


回答3:

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.)



回答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.


回答5:

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



标签: c++ c++11 random