在 random_device 和 seed_seq 之间决定以生成多个随机数序列的种子

AnI*_*ind 2 c++ random c++11

在编写需要多个独立的随机数分布/序列(下面的示例中有两个)的代码时,似乎有两种典型的方法来实现(伪)随机数生成。一种是简单地使用一个random_device对象为两个独立引擎生成两个随机种子:

std::random_device rd;
std::mt19937 en(rd());
std::mt19937 en2(rd());
std::uniform_real_distribution<> ureald{min,max};
std::uniform_int_distribution<> uintd{min,max};
Run Code Online (Sandbox Code Playgroud)

另一个涉及使用random_device对象来创建seed_seq使用多个随机“源”的对象:

// NOTE: keeping this here for history, but a (hopefully) corrected version of
// this implementation is posted below the edit
std::random_device rd;
std::seed_seq seedseq{rd(), rd(), rd()}; // is there an optimal number of rd() to use?
std::vector<uint32_t> seeds(5);
seedseq.generate(seeds.begin(), seeds.end());
std::mt19937 en3(seeds[0]);
std::mt19937 en4(seeds[1]);
std::uniform_real_distribution<> ureald{min,max};
std::uniform_int_distribution<> uintd{min,max};
Run Code Online (Sandbox Code Playgroud)

在这两个中,是否有首选方法?为什么?如果是后者,random_device在生成seed_seq对象时是否有最佳数量的“源” ?

是否有比我上面概述的这两个实现中的任何一个更好的随机数生成方法?

谢谢!


编辑

(希望)修正seed_seq多个发行版的实现版本:

std::random_device rd;
std::seed_seq seedseq1{rd(), rd(), rd()}; // is there an optimal number of rd() to use?
std::seed_seq seedseq2{rd(), rd(), rd()};
std::mt19937 en3(seedseq1);
std::mt19937 en4(seedseq2);
std::uniform_real_distribution<> ureald{min,max};
std::uniform_int_distribution<> uintd{min,max};
Run Code Online (Sandbox Code Playgroud)

Xir*_*ema 6

std::seed_seq 如果您不信任默认实现来正确初始化您正在使用的引擎的状态,则通常打算使用它。

在许多 ?C++11 实现中,std::default_random_engine是 的别名std::mt19937,它是 Mersenne Twister 伪随机数生成算法的特定变体。查看 的规范std::mt19937,我们看到它有一个大小为 624 个无符号整数的状态,这足以保存它打算包含的 19937 位状态(这就是它的名字的由来)。传统上,如果你只用一个uint32_t值(这是你调用rd()一次会得到的值,如果rd是一个std::random_device对象)给它做种子,那么你会留下它的大部分状态未初始化。

现在,对于即将恐慌关于其不良的种子梅森难题引擎谁的好消息是,如果你构建一个std::mt19937与单一uint32_t值(如std::default_random_engine engine{rd()};),实施需要通过重排列的原始种子值初始化状态的剩余部分,因此,虽然单个调用会rd()产生有限范围的实际不同引擎状态,但至少正确初始化引擎仍然足够。这将产生一个“优质”随机数生成器。

但是,如果您担心引擎没有正确设置种子,无论是出于加密原因(尽管请注意,std::mt19937它本身在加密方面并不安全!)或者仅仅是出于统计原因,您可以使用 astd::seed_seq来手动指定整个状态,rd()用于填写每个值,以便您可以在一定程度上保证引擎已正确设置种子。

对于临时使用,或者对于实现高质量随机数不是绝对必要的场景,只需使用单个调用初始化std::random_device::operator()就可以了。

如果您想使用std::seed_seq,请确保正确设置它(原始代码中的示例绝对不正确,至少对于std::mt19937,并且实际上会产生比简单使用更糟糕的结果rd()!)。CodeReview 上的这篇文章包含经过适当审查的代码。

编辑:

对于 Mersenne Twister 的预定义模板,状态大小始终为 19968 位,这比实际需要的略多,但也是使用uint32_t值可以完全表示范围的最小值。这适用于每个 32 位的624 个。因此,如果您打算使用种子序列,则应使用 624 次调用正确地将其初始化为rd()

//Code copied from https://codereview.stackexchange.com/questions/109260/seed-stdmt19937-from-stdrandom-device
std::vector<uint32_t> random_data(624);
std::random_device source;
std::generate(random_data.begin(), random_data.end(), std::ref(source));
std::seed_seq seeds(random_data.begin(), random_data.end());
std::mt19937 engine(seeds);
//Or:
//std::mt19937_64 engine(seeds);
Run Code Online (Sandbox Code Playgroud)

如果你正在使用的非标准实例的工作std::mersenne_twister_engine,需要针对具体情况的状态大小可以通过乘以其查询state_sizeword_size再除以32。

using mt_engine = std::mersenne_twister_engine</*...*/>;
constexpr size_t state_size = mt_engine::state_size * mt_engine::word_size / 32;
std::vector<uint32_t> random_data(state_size);
std::random_device source;
std::generate(random_data.begin(), random_data.end(), std::ref(source));
std::seed_seq seeds(random_data.begin(), random_data.end());
mt_engine engine (seeds);
Run Code Online (Sandbox Code Playgroud)

对于其他发动机类型,您需要逐案评估它们。std::linear_congruential_engine及其预定义的变体使用其字长的单个整数,因此它们只需要一次调用rd()来初始化,因此种子序列是不必要的。我不知道如何std::subtract_with_carry_engine或与其相关的,通过使用std::discard_block_engine工作,但它似乎像他们还只包含一个的状态。