rand()不该再用,因其基于线性同余生成器,周期短、分布不均、低位随机性差,取模导致偏差,全局状态致多线程不安全,且不支持指定分布;C++11推荐用std::random_device+std::mt19937配合标准分布类。

为什么 rand() 真的不该再用了
rand() 的底层实现依赖线性同余生成器(LCG),周期短、分布不均、低位比特随机性极差——尤其当用 rand() % N 取模时,小数值会明显偏多。C++11 起它就被标记为“不鼓励使用”(deprecated in spirit,虽未强制删除,但标准明确推荐替代方案)。更关键的是:rand() 与 srand() 是全局状态,多线程下不安全,且无法指定分布类型(比如你想要正态分布,它给不了)。
用 std::random_device + std::mt19937 替代 rand()
这是最常用、最稳妥的组合:前者提供真随机种子(通常来自操作系统熵池),后者是高质量伪随机数生成器(Mersenne Twister),周期长(2¹⁹⁹³⁷−1)、通过多项统计测试。
实操建议:
- 不要直接用
std::random_device多次生成随机数——它资源有限,有些平台(如 MinGW)甚至返回固定值;只用它初始化种子 - 用
std::mt19937(32 位)或std::mt19937_64(64 位),别用std::default_random_engine,它在不同编译器上可能映射到不同引擎,行为不一致 - 种子建议用
std::random_device{}(),而不是time(nullptr)——后者秒级精度,在快速重复运行时极易撞种子
示例(生成 [1, 6] 的均匀整数):
立即学习“C++免费学习笔记(深入)”;
std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<int> dist(1, 6); int roll = dist(gen); // 不是 gen(),也不是 dist(gen())
选对 std::uniform_*_distribution 才不会出错
很多人以为 “换掉 rand() 就万事大吉”,结果仍用 % 或 static_cast 手动缩放浮点数,这会复现 rand() 的偏差问题。
正确做法是让分布类负责转换:
- 整数范围用
std::uniform_int_distribution<t></t>,闭区间[a, b],不是半开 - 浮点范围用
std::uniform_real_distribution<t></t>,默认半开区间[a, b);若需闭区间,得手动处理边界(比如b + std::numeric_limits<t>::epsilon()</t>) - 避免把同一个
gen对象传给多个不同分布——虽不报错,但会干扰各分布的统计特性(尤其当一个分布频繁调用、另一个极少调用时)
多线程下必须每个线程独占 std::mt19937 实例
std::mt19937 本身不是线程安全的:它的 operator() 会修改内部状态。共享一个实例 + 多线程调用 = 未定义行为(UB),不是偶尔错,是必然崩或静默错误。
常见错误写法:
static std::mt19937 gen(std::random_device{}()); // 全局/静态,多线程共用 → 危险
安全做法:
- 每个线程自己构造
gen(用本地std::random_device种子,或用线程 ID 混合哈希后作为种子) - 或用
thread_local static std::mt19937 gen([]{...}());,确保首次访问时初始化且线程隔离 - 绝对不要用
rand()+omp_set_num_threads()混搭——OpenMP 并行区里调rand()是经典雷区
真正麻烦的不是写几行新代码,而是旧项目里散落各处的 rand() % 100 和隐式全局状态。替换时务必检查所有调用点是否同步更新了种子逻辑和分布对象生命周期。


















