0

I was following this video tutorial (C++/Game Tutorial 8: Random Number Generator). I wrote the program, but every time I get same output.

#include <iostream>
#include <random>
#include <string>
#include <ctime>

using namespace std;

int main()
{

    default_random_engine randomGen(time(0));
    uniform_int_distribution<int> diceRoll(1, 6);

    cout << diceRoll(randomGen) << endl;
    return 0;
}

I use Code::Blocks. I tried it in Visual studio, and it works fine. Does that mean it's Code::Blocks issue?

Jonas Schäfer
  • 20,140
  • 5
  • 55
  • 69
Athul
  • 429
  • 2
  • 5
  • 19
  • ``default_random_engine`` has entirely implementation-defined behaviour. You could try replacing it, e.g., with ``mt19937`` or ``mt19937_64`` (see [relevant article on cppreference.com](http://en.cppreference.com/w/cpp/numeric/random)) and see if that fixes your problem. If it does not, check the output of ``time(0)`` and see if it differs between executions. If it doesn’t, that’s your culprit, although I cannot imagine how *that* would happen consistently unless you run the program several times a second. – Jonas Schäfer Sep 29 '16 at 06:49
  • I treplaced `default_random_engine`with `mt19937` , Now it works. So why is `default_random_engine` gives me the same answer. Is there anyway to solve it? – Athul Sep 29 '16 at 06:52

2 Answers2

5

TL;DR: default_random_engine doesn’t give you any guarantees. If you want numeric random, use mt19937_64 instead. If you want to generate security relevant randomness (keys, IVs, salts, passwords, …), find a CSPRNG seeded from random_device or use random_device. See below for fun analysis of the default_random_engine behaviour of g++.

default_random_engine is implementation-defined

As mentioned in the comments, default_random_engine has implementation-defined behaviour and you should probably not be using it at all. Implementation-defined means that the implementation is, for example, free to use the (in)famous XKCD rng:

class default_random_engine
{
public:
    using result_type = uint32_t;

    result_type min() const
    {
        return 1;
    }

    result_type max() const
    {
        return 6;
    }

    default_random_engine(int) {}; // we don’t care about the argument

    result_type operator()() const
    {
        return 4; // chosen by fair dice roll
                  // guaranteed to be random
    }
};

default_random_engine doesn’t work well with seeds close to each other

However, actually it appears simply that the default_random_engine (used in the GNU libstdc++) is highly biased with the seeds currently produced by time(0) and/or in the resolution it is sampled with a uniform_int_distribution<int>(1, 6). Try to generate several random numbers at once in the same program. You will find outputs like these: 6 6 6 4 6 2 2 3, 6 3 2 1 4 6 3 3, …. In short, default_random_engine has no guarantees, and may be a very bad RNG for any purpose (appearantly, even teaching about the random API, because it doesn’t appear random when generating a single diceroll seeded with UNIX epoch timestamps!).

So I ran the numbers. With a small program (see below), I calculated the probability distribution for the first dice roll number generated with default_random_engine and mt19937_64 to be 6, plotted over the range of seeds. This is with g++ (Debian 6.1.1-11) 6.1.1 20160802 and libstdc++ 6.1.1-11.

Plot of the probability to get a 6 in the first draw over seed

The plot shows the probability to get a 6 with std::uniform_int_distribution<int> (1,6) in the first draw after initialising the corresponding random engine with the seed on the X axis. In addition, the current unix time is drawn as vertical black line (seen only in the top range of the chart). As you can see, we are currently in a range where the integer distribution is very likely (100%) to output a 6 as the first number using default_random_engine. The mt19937_64 in contrast is very close to the expected ⅙ probability. The bin size for the histogram is 10000 (or about 2.8 hours worth of seconds-since-unix-epoch).

Also, over the whole tested range (0..4e9-1), default_random_engine actually produces the 6 as first number with a probability of approximately ⅙. It is just really bad at dealing with seeds which are "close" (±1000) to each other.

Conclusion

So, in a way, time(0) is the culprit, because it is changing slowly. And also, (the standard library used by) Code::Blocks is the culprit because their default_random_engine sucks.A proper RNG should not bias towards a certain result over such a large range of seeds. Just use a proper RNG, and don’t try to get things to work nicely with default_random_engine, which isn’t guaranteed to be portable between compilers and standard libraries.

Use one of the numerically sound random number engines provided by the C++11 standard instead (such as the mentioned mt19937_64), if you need random numbers for numerical purposes (such as a game physics simulation).

If you need random numbers for anything security relevant (generate passwords, salts, IVs for cryptography, keys, …), do not use mt19937_64, but use a CSPRNG seeded using std::random_device or std::random_device directly.

Another note, as the tutorial is geared towards game development: As soon as network comes to play, you may in fact need the platform independent behaviour. In such cases, choosing a specific RNG engine with specific parameters is vital to achieve reproducible results across clients and between client and server.

Source

#include <array>
#include <cstdint>
#include <iostream>
#include <random>
#include <ctime>

void search()
{
    static constexpr uint32_t SEARCH_MAX = 4000000000;
    static constexpr uint32_t SEARCH_BLOCKS = 8;
    static constexpr uint32_t SEARCH_BIN_SIZE = 10000;
    static constexpr uint32_t SEARCH_BINS = SEARCH_MAX / SEARCH_BIN_SIZE;
    static constexpr uint32_t SEARCH_STEP = 1;
    static constexpr uint32_t OUTPUT_STEP = 1000000;
    std::vector<uint32_t> histogram(SEARCH_BINS);
    for (uint32_t &member: histogram) member = 0;


    std::uniform_int_distribution<int> diceRoll(1, 6);
    static constexpr uint32_t START = BLOCK * (SEARCH_MAX/SEARCH_BLOCKS);
    static constexpr uint32_t END = START + SEARCH_MAX/SEARCH_BLOCKS;
    for (uint32_t i = START; i < END; i+=SEARCH_STEP) {
        rng_engine_to_use foo(i);
        int roll = diceRoll(foo);
        if (roll == 6) {
            histogram[i/SEARCH_BIN_SIZE] += 1;
        }
        if (i % OUTPUT_STEP == 0) {
            std::cerr << ((float)i / SEARCH_MAX * 100) << "% \r" << std::flush;
        }
    }

    for (uint32_t i = START / SEARCH_BIN_SIZE; i < END/SEARCH_BIN_SIZE; ++i) {
        std::cout << i*SEARCH_BIN_SIZE << " " << histogram[i] << std::endl;
    }
}

int main()
{
    search();
}

I #defined BLOCK to be in 0..7, to build one version for each of my CPU cores, because the mt19937_64 takes quite some time to generate all those numbers (I only afterwards realised that the plot would not be readable when plotted over the whole range, but meh.). I replaced rng_engine_to_use with mt19937_64 and default_random_engine each and plotted the generated histogram (seen above).

Jonas Schäfer
  • 20,140
  • 5
  • 55
  • 69
  • The final paragraph is absolutely critical! – Martin Bonner supports Monica Sep 29 '16 at 07:39
  • @MartinBonner I edited the structure of my question rather heavily. From the timeline, I assume you are referring to the part about CSPRNGs (which I now also have on the top)? – Jonas Schäfer Sep 29 '16 at 10:39
  • I'm still learning C++, so I didn't understand everything in your answer. Another answer showed(see below) using `std::random_device`, but it also gives me the same output – Athul Sep 29 '16 at 10:54
  • Yup. It was indeed "don't use general purpose random number generators for cryptography". @Athul: Does it help if we spell out CSPRNG = "cryptographically secure random number generator"? – Martin Bonner supports Monica Sep 29 '16 at 11:11
  • @Athul The gist is that ``std::default_random_engine`` is potentially a *bad* random number generator (*even* if seeded with a good seed) and may need more than one "round" of generating random numbers within the same program to get up to speed of generating properly distributed random numbers (and even then, there’s no guarantee). Use mt19937_64 instead for game development (albeit not useful for cryptographic purposes).. Visual Studio has a different default_random_engine than g++, which is why you are seeing differences. That doesn’t happen with mt19937, because it’s specified what it does. – Jonas Schäfer Sep 29 '16 at 11:14
0

As pointed out in this post, in a topic where the PO is following the same Youtube tutorial, it may be time(0) causing this issue. The poster suggests to use std::random_device as a seed.

#include <iostream>
#include <string>
#include <chrono>
#include <random>
#include <ctime>

using namespace std;

int main() {
    default_random_engine randomGenerator(std::random_device{}());
    // OR:
    // default_random_engine randomGenerator(
    //  (unsigned) chrono::system_clock::now().time_since_epoch().count());

    uniform_int_distribution<int> diceRoll(1, 6);

    cout << "You rolled a " << diceRoll(randomGenerator) << endl;

    return 0;
}
Community
  • 1
  • 1
informaticienzero
  • 1,796
  • 1
  • 12
  • 22
  • Sorry, I get same answer here. I tried `mt19937` , that works. I outputted time(0), it changes. – Athul Sep 29 '16 at 06:57