random numbers: rand() vs. <random>

closed account (E0p9LyTq)
I'm adapting some old C++ code that uses the C library random functions, to generate number between the range of 1 - 100, for example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<iostream>
#include <ctime>
#include <cstdlib>

int main()
{
   // seed the random number generator with the current system time
   srand(time(NULL));

   // obtain and display 20 random numbers
   for (int i = 0; i < 20; i++)
   {
      // stores the random number
      int numPicked = rand() % 100 + 1;  / range 1 - 100
      std::cout << numPicked << "\t";
   }
   std::cout << std::endl;

   return 0;
}


I rewrote it as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<iostream>
#include <chrono>
#include <random>

int main()
{
   // obtain a seed from the system clock:
   unsigned seed = static_cast<int> (std::chrono::system_clock::now().time_since_epoch().count());

   // seeds the random number engine, the mersenne_twister_engine
   std::mt19937 generator(seed);

   // set a distribution range (1 - 100)
   std::uniform_int_distribution<int> distribution(1, 100);

   // obtain and display 20 random numbers
   for (int i = 0; i < 20; i++)
   {
      // stores the random number
      int numPicked = distribution(generator);
      std::cout << numPicked << "\t";
   }
   std::cout << std::endl;

   return 0;
}


Did I miss anything that would improve the code? Would the linear_congruential_engine be a better engine choice?
Last edited on
Seems fine to me.
closed account (E0p9LyTq)
@LB,

I seem to recall a while back someone explaining (or linking to a discussion) why using the C++ <random> library gave better distribution on random number distribution versus using the C library srand()/rand() functions.

I did more than a few searches, here and the full internet, for such and I can't seem to find anything.


Do you know of any online resource that explains simply why using the C++ library is the best method for new code?
Last edited on
I am soon to write the FAQ on <random>, but the simple answer is that rand() stinks.

The main problem for distribution is that people don't know how to properly get a random number from rand() -- there is no magic library code to do it for you -- and most people get it Wrong.

The next is that rand's range is often significantly smaller than you want it to be.

You can read all about rand() on it's FAQ:
http://www.cplusplus.com/faq/beginners/random-numbers/

If you want to get technical, rand() implementations tend to favor lower bits, which leads to biased numbers.


The C++ <random> library gives you much more powerful PRNGs with a lot of built-in ways to draw numbers from them without bias.

The most difficult thing to do is seed it properly. Unfortunately, people still depend on the clock -- which is a mistake, but acceptable for video games and the like -- anything that doesn't require statistical correctness (like Monte Carlo simulations and the like).

You need to warm up your Mersenne Twister. generator.discard(10000) or so.

Hope this helps.
closed account (E0p9LyTq)
That FAQ is what I remember reading! Thanks Duoas!

Too bad there is no direct link to the FAQ section from the main CPlusPlus menu. :(

What would you consider a good seed? Is the C++ <chrono> library still not a good means for seeding?

Yeah, that discard() function is one I should have used. Ooops! ;)

Would generator.discard(generator.state_size); give sufficient randomization?
If you are not using MinGW (it did not implement random_device properly):
http://en.cppreference.com/w/cpp/numeric/random/random_device
http://en.cppreference.com/w/cpp/numeric/random/seed_seq

Usually std::mt19937 prng( std::random_device{}() ); is sufficient
Last edited on
closed account (E0p9LyTq)
@MiiNiPaa,

So much to relearn with the C++ <random> library. :)

I'm using both VC++ 2015 and TDM-GCC 4.9.2 (Orwell's Dev-C++ fork).

I have been considering shifting away from Dev-C++ and using TDM-GCC (or similar) via command line tools. Some day. ;)
FurryGuy wrote:
I have been considering shifting away from Dev-C++ and using TDM-GCC (or similar) via command line tools. Some day. ;)
I'd recommend nuwen MinGW:
http://nuwen.net/mingw.html
closed account (E0p9LyTq)
@LB,

can nuwen MinGW create 64-bit exes?

I'll still check the link out, thanks. :)

@MiiNiPaa,

I tried to use random_device in Dev-C++. Yeah, it isn't implemented properly. :\ Same random number series. boo!

FurryGuy wrote:
can nuwen MinGW create 64-bit exes?
The distribution is 64bit, so that's the default.
Unfortunately, random_device is specified to possibly throw just about anytime you look at it. You must use it carefully.

If your STL is broken, use Boost's. Most modern OSs provide a TRNG that is appropriate for generating a proper seed sequence. (That is, don't just seed with a single integer. Pull from the TRNG. Or, as has been written elsewhere, "use /dev/urandom, use /dev/urandom, use /dev/urandom!" [if I remember the quote correctly].)

Some further reading:
http://www.pcg-random.org/posts/cpp-seeding-surprises.html
(Be careful with that site: some of the information is just plain wrong, such as when he suggests getting entropy from the PID, etc.)

http://www.2uo.de/myths-about-urandom/

Seed your PRNGs with a full state pulled from the TRNG -- which random_device will use. Only if you cannot get the TRNG/CSPRNG to work should you fall back on the system's high-resolution time or quit, depending on the security requirements of your application.
> Would generator.discard(generator.state_size); give sufficient randomization?

Ideally, to eliminate bias, use a seed sequence. http://en.cppreference.com/w/cpp/numeric/random/seed_seq

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <random>
#include <ctime>
#include <algorithm>

std::mt19937 initialize_twister( std::size_t seed = std::time(nullptr) )
{
    static constexpr std::size_t NDISCARD = 1024 ;
    std::minstd_rand lcg(seed) ;
    lcg.discard(NDISCARD) ;

    std::size_t seeds[ std::mt19937::state_size ] ;
    std::generate_n( seeds, std::mt19937::state_size, lcg ) ;
    try { seeds[0] = std::random_device{}() ; } catch( const std::exception& ) { /* ignore */ }

    std::seed_seq seed_sequence( std::begin(seeds), std::end(seeds) ) ;
    return std::mt19937 { seed_sequence } ; // warm-up with seed seed_sequence.generate()
}
closed account (E0p9LyTq)
@JLBorges,

with your code snippet as a starting point I rewrote my source as follows:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>
#include <random>
#include <chrono>
#include <algorithm>

std::mt19937 initialize_twister(std::size_t seed = std::chrono::system_clock::now().time_since_epoch().count())
{
   static constexpr std::size_t NUM_DISCARD = 10240;

   std::minstd_rand lcg(seed);
   lcg.discard(NUM_DISCARD);

   std::size_t seeds[std::mt19937::state_size];
   std::generate_n(seeds, std::mt19937::state_size, lcg);

   try
   {
      // check if there is a random_device available
      seeds[0] = std::random_device {}();
   }
   catch (const std::exception&)
   {
      /* ignore */
   }

   std::seed_seq seed_sequence(std::begin(seeds), std::end(seeds));
   return  std::mt19937 {seed_sequence}; // warm-up with seed seed_sequence.generate()
//   return seed_sequence;
}

int main()
{
   std::mt19937 generator = initialize_twister();

   // set a distribution range (1 - 100)
   std::uniform_int_distribution<int> distribution(1, 100);

   // obtain and display 50 random numbers
   for (int i = 0; i < 50; i++)
   {
      // stores the random number
      int numPicked = distribution(generator);
      std::cout << numPicked << "\t";
   }
   std::cout << std::endl;

   return 0;
}


Little bit by little bit I'm hopefully getting a grasp on what C++ brings to the toolbox with random number generation. My brain is stuffed to overflow at the moment. ;)
Topic archived. No new replies allowed.