notaorb
notaorb

Reputation: 2180

Random number generation, two different methods?

Does anyone know the differences between the two methods of random number generation used in the following code? My suspicion is the first case may be more computationally expensive because it's getting a new seed from /dev/urandom every time a random number is generated. Also appreciate any comments if I'm generating random numbers correctly in these examples.

I'm confused because uniform_int_distribution specifies template<class URNG> result_type operator()(URNG& g); and in some examples I see the parameter passed to be of the type default_random_engine and other time random_device. This adds confusion to what default_random_engine actually does.

For example, method 1 I have seen:

#include <iostream>
#include <random>
#include <map>
using namespace std;

int main()
{
    random_device rd;
    uniform_int_distribution<int> p{0,9};

    map<int,int> m;
    for (int i=0; i < 100; ++i) {
        m[p(rd)]++;
    }

    for (map<int,int>::iterator it = m.begin();
         it != m.end(); ++it)
        cout << it->first << ", " << it->second << '\n';
    
    return 0;
}

Method 2,

#include <iostream>
#include <random>
#include <map>
using namespace std;

int main()
{
    random_device rd;
    default_random_engine gen(rd());
    uniform_int_distribution<int> p{0,9};

    map<int,int> m;
    for (int i=0; i < 100; ++i) {
        m[p(gen)]++;
    }

    for (map<int,int>::iterator it = m.begin();
         it != m.end(); ++it)
        cout << it->first << ", " << it->second << '\n';
    
    return 0;
}

Upvotes: 0

Views: 106

Answers (2)

t.niese
t.niese

Reputation: 40842

The std library offers a set of Predefined random number generators that gives certain guarantees about the values generated. One of them is default_random_engine which is implementation-defined (so you need to check how the vendor of the library implemented it).

To have a reliable generator (consistent results between the different vendors) you would choose one of the others like mt19937.

The random numbers you get from random_device are normally only used as a seed for the chosen random number generator. For random_device you have a similar problem as with default_random_engine as it is not specified how the values are generated.

The idea behind this is that random_device provides good enough values for a seed in general but not for not good enough for a proper generator.

If you know the specific hardware your program is running on and the library, and you know that random_device creates random numbers based on a good source, then it might be an option, but if you create an application that is used on different OS and unknown hardware, then random_device won't be an option as a generator.

Upvotes: 1

sweenish
sweenish

Reputation: 5202

The first method is bad practice, and the second is preferred. Although, in the second method, default_random_engine is typically not recommended unless it's for experimenting or some other non-important reason. The big reason is that's implementation-defined, so it will differ based on compiler, and it generally is not robust at all, just simple.

The reasoning that the first method is bad is that std::random_device polls your hardware's source of true randomness. This is a limited supply. The better practice is to use it as a completely unpredictable seed to a pseudo-random number generator (PRNG). This is why the second example is better.

I've made a couple changes to your second example. The main change is that I don't need to declare a random_device to sit around on the stack, and I use std::mt19937 which is probably the most common PRNG you'll see in C++ code.

None of the C++ Standard Library PRNGs are considered sufficient for cryptographic purposes; you'd need an external library for that.

#include <iostream>
#include <map>
#include <random>

int main() {
  std::mt19937 gen(std::random_device{}());
  std::uniform_int_distribution<int> p{0, 9};

  std::map<int, int> m;
  for (int i = 0; i < 100; ++i) {
    m[p(gen)]++;
  }

  for (auto it = m.begin(); it != m.end(); ++it) {
    std::cout << it->first << ", " << it->second << '\n';
  }

  return 0;
}

Upvotes: 3

Related Questions