C++ 随机库将确定性的引擎与分布分开。引擎在规定的输出范围内产生均匀分布的值; 分布消费引擎输出,并将其映射到所需的统计分布。当状态必须继续时,应让两个对象都保持存活。
#include <random>
std::mt19937_64 engine{std::random_device{}()};
std::uniform_int_distribution<int> die{1, 6};
int roll = die(engine); // Both engine and distribution may advance state.
std::mt19937 和 std::mt19937_64 是伪随机引擎:固定初始状态会产生可复现序列。
std::default_random_engine 由实现定义,因此当测试、文件或协议要求不同实现产生相同序列时不适用。
根据需求设定种子 #
确定性测试应使用明确且有文档说明的种子。当生成数据必须复现时,在测试失败时记录种子。
std::mt19937 engine{0x5EEDu};当实现具有合适来源时,std::random_device 用于提供非确定性随机数,但实现也可能提供确定性回退。
其 entropy() 值是估计值;没有可用估计时可能为零。因此,random_device 适合作为非加密模拟的尽力而为种子来源,
不能作为可移植的加密安全保证。对于密钥、令牌、盐值和 nonce,应使用操作系统的加密随机 API
或经过审查的加密库。
std::seed_seq 可以将多个源字扩展为引擎状态。当需要独立流时,避免用相同的小整数为多个长期引擎设种子;
应建立并记录流分配策略。
分布产生有界值且没有模偏差 #
使用表达所需结果的分布:
std::uniform_int_distribution<int> index{0, static_cast<int>(size - 1)};
std::bernoulli_distribution enabled{0.25};
std::normal_distribution<double> noise{0.0, 1.0};uniform_int_distribution 的边界包含在内。构造或参数化它之前验证动态边界:
a > b 的范围违反分布的前置条件。除非引擎的输出范围能整除 n,否则不要使用 engine() % n
获取有界均匀结果,因为某些余数会出现得更频繁。标准分布会为其指定的分布执行必要的拒绝或映射处理。
分布不能与引擎互换。分布可以保留状态,例如正态分布实现可能缓存一个已生成的值; 应根据模拟的可复现性需求保留或重置分布状态。
生命周期、可复现性和序列化 #
按值传递引擎会有意创建独立的序列状态。当辅助函数必须推进调用方序列时,按引用传递。
int roll_die(std::mt19937_64& engine) {
static std::uniform_int_distribution<int> die{1, 6};
return die(engine);
}这里的 static 分布只有在调用已同步时才安全,因为它可能具有可变状态。
当不同模拟的参数或状态不同时,最好明确分布的所有权。
标准引擎支持格式化的流输入和输出状态,从而支持在同一标准引擎规范内建立检查点。 应将引擎类型、种子策略和分布参数与序列化状态一同持久化。 序列化序列不是加密秘密存储;项目不应宣称超出所选引擎和实现环境保证范围的跨版本可复现性。
并发 #
引擎和分布对象是可变状态,未经同步不得并发修改。 当流彼此独立时,为每个线程提供自己的引擎和分布;当需要一个有序序列时, 使用互斥锁保护共享生成器。线程本地生成器需要有意设计的种子策略; 简单地将相同种子复制到每个线程会产生相关的相同流,而不是并行随机性。
评论
在 GitHub 上阅读或评论(发表评论需要登录)