C++ 随机数设施

最后更新: 2024-10-30 00:23:14 +0800 +0800
原文以英文撰写。您正在阅读AI翻译版本。

C++ 随机库将确定性的引擎与分布分开。引擎在规定的输出范围内产生均匀分布的值; 分布消费引擎输出,并将其映射到所需的统计分布。当状态必须继续时,应让两个对象都保持存活。

cpp
#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 由实现定义,因此当测试、文件或协议要求不同实现产生相同序列时不适用。

根据需求设定种子 #

确定性测试应使用明确且有文档说明的种子。当生成数据必须复现时,在测试失败时记录种子。

cpp
std::mt19937 engine{0x5EEDu};

当实现具有合适来源时,std::random_device 用于提供非确定性随机数,但实现也可能提供确定性回退。 其 entropy() 值是估计值;没有可用估计时可能为零。因此,random_device 适合作为非加密模拟的尽力而为种子来源, 不能作为可移植的加密安全保证。对于密钥、令牌、盐值和 nonce,应使用操作系统的加密随机 API 或经过审查的加密库。

std::seed_seq 可以将多个源字扩展为引擎状态。当需要独立流时,避免用相同的小整数为多个长期引擎设种子; 应建立并记录流分配策略。

分布产生有界值且没有模偏差 #

使用表达所需结果的分布:

cpp
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 获取有界均匀结果,因为某些余数会出现得更频繁。标准分布会为其指定的分布执行必要的拒绝或映射处理。

分布不能与引擎互换。分布可以保留状态,例如正态分布实现可能缓存一个已生成的值; 应根据模拟的可复现性需求保留或重置分布状态。

生命周期、可复现性和序列化 #

按值传递引擎会有意创建独立的序列状态。当辅助函数必须推进调用方序列时,按引用传递。

cpp
int roll_die(std::mt19937_64& engine) {
    static std::uniform_int_distribution<int> die{1, 6};
    return die(engine);
}

这里的 static 分布只有在调用已同步时才安全,因为它可能具有可变状态。 当不同模拟的参数或状态不同时,最好明确分布的所有权。

标准引擎支持格式化的流输入和输出状态,从而支持在同一标准引擎规范内建立检查点。 应将引擎类型、种子策略和分布参数与序列化状态一同持久化。 序列化序列不是加密秘密存储;项目不应宣称超出所选引擎和实现环境保证范围的跨版本可复现性。

并发 #

引擎和分布对象是可变状态,未经同步不得并发修改。 当流彼此独立时,为每个线程提供自己的引擎和分布;当需要一个有序序列时, 使用互斥锁保护共享生成器。线程本地生成器需要有意设计的种子策略; 简单地将相同种子复制到每个线程会产生相关的相同流,而不是并行随机性。

评论

在 GitHub 上阅读或评论(发表评论需要登录)