在random_device和seed_seq之间做出决定,为多个随机数序列生成种子
Deciding between random_device and seed_seq to generate seeds for multiple random number sequences
当编写需要多个独立随机数分布/序列的代码时(下面有两个示例),似乎有两种典型的方法可以实现(伪)随机数生成。一种是简单地使用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};
另一个涉及使用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};
在这两种方法中,有没有首选方法?为什么?如果是后者,是否有用于生成seed_seq
对象的最佳数量的random_device
"源"?
有没有比我上面概述的这两种实现更好的随机数生成方法?
谢谢!
编辑
(希望)更正了多个发行版的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};
如果您不信任默认实现来正确初始化正在使用的引擎的状态,则通常打算使用std::seed_seq
。
在许多≥C++11实现中,std::default_random_engine
是std::mt19937
的别名,这是Mersenne Twister伪随机数生成算法的特定变体。查看std::mt19937
的规范,我们看到它的状态大小为 624 个无符号整数,这足以容纳它打算包含的 19937 位状态(这就是它的名字的由来)。传统上,如果您只使用单个uint32_t
值为其设定种子(如果rd
是std::random_device
对象,则从调用rd()
一次会得到该值),那么您将保留其绝大多数状态未初始化。
现在,对于任何即将对他们种子不佳的 Mersenne Twister 引擎感到恐慌的人来说,好消息是,如果您使用单个uint32_t
值(如std::default_random_engine engine{rd()};
)构造一个std::mt19937
,则需要实现通过排列原始种子值来初始化状态的其余部分,因此,虽然对rd()
的单次调用会产生有限范围的实际不同引擎状态, 至少正确初始化引擎仍然足够。这将产生一个"高质量"随机数生成器。
但是,如果您担心引擎没有正确设定种子,无论是出于加密原因(尽管请注意std::mt19937
本身在加密上并不安全!)还是仅仅是出于统计原因,您可以使用std::seed_seq
手动指定整个状态,使用rd()
填充每个值,以便您可以相对自信地保证引擎已正确设定种子。
对于随意使用或并非绝对需要实现高质量随机数的场景,只需通过一次调用std::random_device::operator()
进行初始化即可。
如果你想使用一个std::seed_seq
,请确保你正确地设置了它(你的原始代码中的例子绝对不正确,至少对于std::mt19937
来说,实际上会产生比简单地使用rd()
更糟糕的结果!这篇关于CodeReview的文章包含经过适当审查的代码。
编辑:
对于 Mersenne Twister 的预定义模板,状态大小始终为 19968 位,略大于实际需要,但也是可以使用uint32_t
值完全表示范围的最小值。这相当于 624 个单词,每个单词32 位。因此,如果您计划使用种子序列,您将通过 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);
如果您使用的是std::mersenne_twister_engine
的非标准实例化,则可以通过将state_size
乘以word_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);
对于其他引擎类型,您需要根据具体情况对其进行评估。std::linear_congruential_engine
及其预定义的变体使用其字大小的单个整数,因此它们只需要一次调用rd()
即可初始化,因此种子序列是不必要的。我不确定std::subtract_with_carry_engine
或其相关的使用std::discard_block_engine
如何工作,但似乎它们也只包含一个状态词。
- 在决定是通过参考还是通过价值时,尺寸真的是一个问题吗
- 如何使用要传递给 mt19937 的可选随机种子参数设计函数
- 从给定种子生成相同的随机数序列C++
- C++递归来决定数组中的两个值
- 如何使用可选的随机种子参数创建 roll_die() 函数
- 你如何决定将C++和/或 python 中的成员函数表示为静态?
- 是否可以为boost::random::uniform_int_distribution<>设置确定性种子?
- 使用给定种子生成的随机数序列是否保证在标准版本中相同?
- 在编译时生成某种子类/类型注册表?
- libtorrent是否支持带摘要身份验证的http web种子
- 我的随机生成器是否不工作,或者我决定人/骨架是否击中对手的方式是否有错误
- OpenMP:决定它们是否是素数
- 我应该使用std::seed_seq来种子std::mt19937吗
- gcc 如何决定隐式包含哪些库?
- 除了 rand() 之外,是否有任何随机生成器库,开发人员可以手动设置种子?
- 如果我决定输入 (1 2) 而它要求 x,为什么我的输出会出错,但如果我输入 (12) 工作正常
- 随机数生成器的种子和状态有什么区别?
- GCC (libstdc++) 运行时如何在异常处于活动状态时决定终止 ()
- 当存在嵌套循环时,我应该如何决定应该嵌套哪个循环?
- 在random_device和seed_seq之间做出决定,为多个随机数序列生成种子