简体   繁体   English

在编译时用 C++ 生成随机数

[英]Generate random numbers in C++ at compile time

I'm trying to precompute random values using C++11's random library at compile time.我正在尝试在编译时使用 C++11 的random库预先计算随机值。 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;
    };
};

Only constexpr functions and constant expressions may be evaluated at compile time.在编译时只能计算constexpr函数和常量表达式。 That rules out <chrono> and <random> .这排除了<chrono><random>

What you can do is access the __TIME__ preprocessor macro and define your own PRNG composed of one-line, constexpr functions.您可以做的是访问__TIME__预处理器宏并定义您自己的由单行constexpr函数组成的 PRNG。

There is a research paper on the topic: Random number generator for C++ template metaprograms containing code snippet for the __TIME__ trick.有一篇关于该主题的研究论文:包含__TIME__技巧的代码片段的C++ 模板元程序的随机数生成器 It also talks about supporting different random number engines and distributions as orthogonal choices.它还谈到支持不同的随机数引擎和分布作为正交选择。

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.一个非常简单的例子是使用 compile 命令中定义的宏变量来编译你的程序。 Here $RANDOM is a special built-in variable in unix/linux systems that automatically returns a random 16-bit number.这里的$RANDOM是 unix/linux 系统中一个特殊的内置变量,它会自动返回一个随机的 16 位数字。

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.更好的随机生成器是编写一个使用音频和视觉输入的程序。

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).编译器会限制他们优化的积极程度,所以我不能保证他们总是会进行这种替换,而且我怀疑任何编译器都能够替换像 Mersenne Twister 这样复杂的东西,但像linear_congruential_engine这样更简单的东西有机会(此外,确保它发生的唯一方法是让编译器输出汇编代码,然后查看汇编代码)。

I know this is possible because I implemented a random generator modeled after random_device that used Marsaglia's Xorshift algorithm .我知道这是可能的,因为我实现了建模的随机生成random_device所用马尔萨利亚的Xorshift算法 Since Marsaglia's paper actually included multiple related algorithms, I had the class take a template parameter to select which shift pattern to use.由于 Marsaglia 的论文实际上包含了多个相关的算法,因此我让该类采用模板参数来选择要使用的换档模式。 I wanted to know if the compiler would optimize out the switch statement I used.我想知道编译器是否会优化我使用的switch语句。 I forgot to pass a seed, so the compiler used the default, ie, 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."当我查看汇编代码时,不仅switch没有了,而且 GCC 将程序优化为“输出这三个数字”。

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

不仅system_clock::now()在编译时不可知,而且您的函数被标记为返回 bool,但在任何地方都没有 return 语句。

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 .变量rands未在main的范围内声明。 Make it a global variable instead of local in generate_random and that error will go away.generate_random中将其设为全局变量而不是本地变量,该错误就会消失。

This question may now be nine years old but I still did not find any satisfying solution so I implemented a reliable compile time random generator as a single header library myself.这个问题现在可能已经九年了,但我仍然没有找到任何令人满意的解决方案,所以我自己实现了一个可靠的编译时随机生成器作为单个头库。 It uses KISS random generator and represents its internal state as a parameterized type.它使用 KISS 随机生成器并将其内部状态表示为参数化类型。 The state is pseudo remembered using the counter macro.使用计数器宏伪记住状态。 CTRandomTimeSeed is an attempt to implement a varring seed between multiple compilations. CTRandomTimeSeed尝试在多次编译之间实现可变种子。 Additionally a custom seed can be defined using DYNLEC_CUSTOM_RANDOM_SEED .此外,可以使用DYNLEC_CUSTOM_RANDOM_SEED定义自定义种子。

#pragma once

#include <cstdint>

// all numbers are generated randomly at compile time. the internal state is pseudo
// remembered using the counter macro. the seed is based on time using the timestamp
// and time macro. additionally a custom random seed can be specified to fully rely

#ifndef DYNLEC_CUSTOM_RANDOM_SEED
#define DYNLEC_CUSTOM_RANDOM_SEED 0xbdacf99b3f7a1bb4ULL
#endif

// just iterating over the macros will always result in same
// number because the internal state is only updated for each occurance
// of the following macros

// generates a random number seeded with time and the custom seed
#define DYC_RAND_NEXT (::Dynlec::CTRandomGeneratorValueSeeded<__COUNTER__>)
// generates a random number seeded with time and the custom seed between min and max ( [min, max[ )
#define DYC_RAND_NEXT_BETWEEN(min, max) (min + (::Dynlec::CTRandomGeneratorValueSeeded<__COUNTER__> % (max - min)))
// generates a random number seeded with time and the custom seed with a limit ( [0, limit[ )
#define DYC_RAND_NEXT_LIMIT(limit) DYC_RAND_NEXT_BETWEEN(0, limit)
// helper macro for non repetetive indexed values
#define DYC_RAND_INDEXED(index) (::Dynlec::CTRandomGeneratorValue<index, ::Dynlec::CTRandomSeed ^ DYC_RAND_NEXT>)
// helper macro for non repetetive random streams
#define DYC_RAND_STREAM(n, callback) (::Dynlec::CTRandomStream<n, ::Dynlec::CTRandomSeed ^ DYC_RAND_NEXT>)

namespace Dynlec
{
    // the random generator internal state is represented by
    // the CTRandomGeneratorRaw type with each of its values
    // x, y, z and c
    template <
        uint64_t x, 
        uint64_t y, 
        uint64_t z, 
        uint64_t c>
    class CTRandomGeneratorRaw
    {
        static_assert(y != 0, 
            "CompileTimeRandom can not be used with 'y' equals 0");
        static_assert(z != 0 || c != 0,
            "CompileTimeRandom can not be used with 'z' and 'c' equals 0");
    public:
        typedef CTRandomGeneratorRaw<
            6906969069ULL * x + 1234567ULL,
            ((y ^ (y << 13)) ^ ((y ^ (y << 13)) >> 17)) ^ (((y ^ (y << 13)) ^ ((y ^ (y << 13)) >> 17)) << 43),
            z + ((z << 58) + c),
            ((z + ((z << 58) + c)) >> 6) + (z + ((z << 58) + c) < ((z << 58) + c))> Next;

        constexpr static uint64_t Value = x + y + z;
    };

    // to prevent any accidental selection of invalid parameters
    // these values are omitted
    template <
        uint64_t x,
        uint64_t y,
        uint64_t z,
        uint64_t c>
    class CTRandomGeneratorRawSafe
        :
        public CTRandomGeneratorRaw<
            x, (y == 0) ? 1 : y, (z == 0 && c == 0) ? 1 : z, c>
    {
    };

    // CTRandomGenerator is used to quickly compute the nth iteration
    // of CTRandomGeneratorSafeRaw based on a single uint64_t seed
    template <uint64_t iterations, uint64_t seed>
    class CTRandomGenerator
    {
        friend CTRandomGenerator<iterations + 1, seed>;
        typedef typename CTRandomGenerator<iterations - 1, seed>::Current::Next Current;

    public:
        constexpr static uint64_t Value = Current::Value;
    };

    template <uint64_t seed>
    class CTRandomGenerator<0ULL, seed>
    {
        friend CTRandomGenerator<1ULL, seed>;

        typedef typename CTRandomGeneratorRawSafe<
            seed ^ 1066149217761810ULL,
            seed ^ 362436362436362436ULL,
            seed ^ 1234567890987654321ULL,
            seed ^ 123456123456123456ULL>::Next Current;

    public:
        constexpr static uint64_t Value = Current::Value;
    };

    template <uint64_t iteration, uint64_t seed>
    constexpr static uint64_t CTRandomGeneratorValue = CTRandomGenerator<iteration, seed>::Value;

    const uint64_t CTRandomTimeSeed = 
        CTRandomGeneratorValue<0, (__TIME__[0]) ^
        CTRandomGeneratorValue<0, (__TIME__[1]) ^
        CTRandomGeneratorValue<0, (__TIME__[3]) ^
        CTRandomGeneratorValue<0, (__TIME__[4]) ^
        CTRandomGeneratorValue<0, (__TIME__[6]) ^
        CTRandomGeneratorValue<0, (__TIME__[7])>>>>>> ^
        CTRandomGeneratorValue<0, (__TIMESTAMP__[0]) ^
        CTRandomGeneratorValue<0, (__TIMESTAMP__[1]) ^
        CTRandomGeneratorValue<0, (__TIMESTAMP__[2]) ^
        CTRandomGeneratorValue<0, (__TIMESTAMP__[4]) ^
        CTRandomGeneratorValue<0, (__TIMESTAMP__[5]) ^
        CTRandomGeneratorValue<0, (__TIMESTAMP__[6])>>>>>> ^
        CTRandomGeneratorValue<0, (__TIMESTAMP__[8]) ^
        CTRandomGeneratorValue<0, (__TIMESTAMP__[9]) ^
        CTRandomGeneratorValue<0, (__TIMESTAMP__[20]) ^
        CTRandomGeneratorValue<0, (__TIMESTAMP__[21]) ^
        CTRandomGeneratorValue<0, (__TIMESTAMP__[22]) ^
        CTRandomGeneratorValue<0, (__TIMESTAMP__[23])>>>>>>;

    const uint64_t CTRandomSeed = (DYNLEC_CUSTOM_RANDOM_SEED ^ CTRandomTimeSeed);

    template <uint64_t iteration>
    constexpr static uint64_t CTRandomGeneratorValueSeeded = CTRandomGeneratorValue<iteration, CTRandomSeed>;

    template <uint64_t n, uint64_t seed = ::Dynlec::CTRandomSeed>
    struct CTRandomStream
    {
        // callback(uint64_t index [0;n[, uint64_t random_number)
        template <typename T>
        static void Call(T callback)
        {
            CTRandomStream<n - 1, seed>::Call(callback);
            callback(n - 1, CTRandomGeneratorValue<n, seed>);
        }
    };

    template <uint64_t seed>
    struct CTRandomStream<0, seed>
    {
        template <typename T>
        static void Call(T callback) { }
    };
}

Usage example:用法示例:

// random 64 bit number
std::cout << DYC_RAND_NEXT << std::endl;

// random 64 bit number between [0 and 10[
std::cout << DYC_RAND_NEXT_LIMIT(10) << std::endl;

// random 64 bit number between [1 and 10]
std::cout << DYC_RAND_NEXT_BETWEEN(1, 11) << std::endl;

// initialize array with random numbers
int array[50];

Dynlec::CTRandomStream<50>::Call([&array](uint64_t index, uint64_t n)
    { 
        array[index] = n;
    });

Link to the github project containing the project.链接到包含该项目的 github 项目。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM