Generate random numbers in C++ at compile time

2019-01-17 15:25发布

问题:

I'm trying to precompute random values using C++11's random library at compile time. I'm mostly following examples. What am I doing wrong here?

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

vector<double> rands;
typedef std::mt19937_64 RNG;
uint64_t seed_val;
RNG rng; 

void initialize() {
     rng.seed(seed_val);
}

constexpr vector<double> generate_random( )                 //size_t numbers)
{   
    int numbers = 1000;
    std::uniform_real_distribution<double> zero_one(0.0, 1.0);
        for (unsigned int i = 0; i < numbers; i++) { 
             double rand_num = zero_one(rng);
             rands.push_back( rand_num );
    }
    return rands;
}

int main()
{
    cout << "TMP rands";
    for_each( rands.begin(), rands.end(), [] (double value)
    {
        cout<<value<<endl;
    });
}

Here's an example compile-time random number generator shamelessly stolen from here, but thought it might be useful for anyone who looks this up:

template<u32 S, u32 A = 16807UL, u32 C = 0UL, u32 M = (1UL<<31)-1>
struct LinearGenerator {
    static const u32 state = ((u64)S * A + C) % M;
    static const u32 value = state;
    typedef LinearGenerator<state> next;
    struct Split { // Leapfrog
        typedef LinearGenerator< state, A*A, 0, M> Gen1;
        typedef LinearGenerator<next::state, A*A, 0, M> Gen2;
    };
};

回答1:

Only constexpr functions and constant expressions may be evaluated at compile time. That rules out <chrono> and <random>.

What you can do is access the __TIME__ preprocessor macro and define your own PRNG composed of one-line, constexpr functions.



回答2:

I would try pulling it in from an external source. A very simple example is to compile your program with defined macro variables in the compile command. Here $RANDOM is a special built-in variable in unix/linux systems that automatically returns a random 16-bit number.

g++ -D__RANDOM__=$RANDOM yourprog.cpp -o yourprog

//yourprog.cpp
#include <iostream>
int main() {
  std::cout << "Random variable " << __RANDOM__ << std::endl;
  return 0;
}

You can also write your own script or executable to assign to your macro variable.

//DevRandomGenerator.cpp
#include <iostream>
#include <fstream>

class DevRandom {
private:
    std::ifstream stream;
public:

    DevRandom() {
        stream.open("/dev/urandom",std::ios::in|std::ios::binary);
    }

    unsigned int unsignedInt() {
        unsigned int u = 0;
        stream.read((char*)&u, sizeof(unsigned int));
        return u;
    }
};

int main() {
  DevRandom rand;
  std::cout << rand.unsignedInt() << std::endl;
  return 0;
}

then compile as:

g++ DevRandomGenerator.cpp -o DevRandomGenerator
g++ -D__RANDOM__="$(./DevRandomGenerator)" yourprog.cpp -o yourprog

A better random generator would be to write a program that uses audio and visual inputs.



回答3:

Not just is system_clock::now() not compile-time knowable, but your function is labeled as returning a bool, but has no return statement anywhere.



回答4:

There is a research paper on the topic: Random number generator for C++ template metaprograms containing code snippet for the __TIME__ trick. It also talks about supporting different random number engines and distributions as orthogonal choices.



回答5:

I know this question is five years old, and already has an accepted answer. Even so, I would like to add that it certainly is possible to generate random numbers at compile time, with the understanding that you'll get the same sequence of random numbers each time you run the program. To put it simply, if the seed is known at compile time, the compiler is allowed to figure out what random numbers will be output, and just turn the program into "output this sequence of numbers."

Compilers will have limits to how aggressively they optimize, so I can't promise that they will always make this substitution, and I doubt any compiler would be able to make the substitution for something as complex as the Mersenne Twister, but something simpler like linear_congruential_engine has a chance (also, the only way to be sure that it happened would be to have the compiler output assembly code, and then you look at the assembly code).

I know this is possible because I implemented a random generator modeled after random_device that used Marsaglia's Xorshift algorithm. Since Marsaglia's paper actually included multiple related algorithms, I had the class take a template parameter to select which shift pattern to use. I wanted to know if the compiler would optimize out the switch statement I used. I forgot to pass a seed, so the compiler used the default, i.e., the seed was known at compile time. When I looked at the assembly code, not only was the switch gone, but GCC had optimized the program into "output these three numbers."

The final version of the program listed in the question never actually called the functions to generate the sequence of numbers, and never called the function to seed the generator. This version will do that, but I doubt it will be turned into "print this sequence of random numbers."

#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <iterator>
#include <random>

int get_seed()
{
    int hour = std::atoi(__TIME__);
    int min = std::atoi(__TIME__ + 3);
    int sec = std::atoi(__TIME__ + 6);
    return 10000 * hour + 100 * min + sec;
}

int main()
{
    // get_seed() returns an int based on __TIME__ (a string literal
    // set by the preprocessor), which is known at compile time.
    //
    // Also, w/r/t the engines in <random>: not setting a seed explicitly
    // will use a default seed, which is known at compile time.  So if
    // you're OK getting the same sequence of numbers for any compilation,
    // then "std::mt19937_64 rng;" may be all you need.
    std::mt19937_64 rng(get_seed());
    std::uniform_real_distribution<double> zero_one(0.0, 1.0);
    const int COUNT = 1000;
    std::generate_n(std::ostream_iterator<double>(std::cout, "\n"), COUNT,
        [&rng, &zero_one]() { return zero_one(rng); });
    return 0;
}


回答6:

As per the error message:

cpp11tmprands.cpp:22:15: error: ‘rands’ was not declared in this scope

The variable rands is not declared in the scope of main. Make it a global variable instead of local in generate_random and that error will go away.