std::uniform_real_distribution 仅线性映射均匀随机数,无法直接生成非均匀分布;需通过逆变换采样(如指数分布用 -log(u)/λ)或拒绝采样等方法显式转换。

为什么 std::uniform_real_distribution 不能直接生成非均匀分布?
它只负责把均匀分布的随机比特“线性映射”到指定区间,不涉及概率密度变换。要得到服从目标分布(比如指数、高斯、自定义 PDF)的随机数,必须显式完成从均匀分布到目标分布的变量变换——核心是逆变换采样(Inverse Transform Sampling)或拒绝采样(Rejection Sampling)。
常见错误是试图用多个 uniform_real_distribution 拼凑出非均匀效果,比如“多掷几次取平均”,这只会趋近正态(中心极限定理),但无法精确控制 PDF 形状,也不高效。
- 真正可控的方式:先有目标
pdf(x)或cdf(x),再选采样策略 -
cdf(x)可解析反解 → 用std::pow、std::log、std::erf_inv(需自实现或 Boost)求cdf_inv(u) -
cdf(x)不可解析反解 → 改用分段线性插值 + 二分查找,或拒绝采样(需构造包络函数)
用逆变换法生成指数分布:三行关键代码
指数分布 PDF 是 lambda * exp(-lambda * x),CDF 是 1 - exp(-lambda * x),其反函数为 -log(1 - u) / lambda。由于 u 和 1-u 在 [0,1) 上同分布,可简化为 -log(u) / lambda,避免浮点下溢风险。
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<double> uni(0.0, 1.0);
<p>auto exp_sample = [&](double lambda) {
double u = uni(gen);
return -std::log(u) / lambda; // 注意:u 不可能为 0,log 安全
};- 不要用
std::log(1.0 - u):当u接近 0 时,1.0 - u会损失精度,导致 log 结果偏差 - lambda 越大,均值越小,生成的数越集中在 0 附近;该公式无分支、无循环,单次调用仅约 15–20 个 CPU 周期
- 若需大批量生成,可向量化:用 SIMD 加载 4/8 个
u,批量计算-log(u),但需确保 math 库支持(如 Intel SVML 或 std::experimental::simd)
自定义任意 PDF:用分段线性 CDF 查表加速
当只有离散点 {x_i, pdf_i}(比如实测直方图),无法写出解析 CDF,此时应预计算归一化后的分段线性 CDF 表,再对均匀随机数做二分查找 + 线性插值。
立即学习“C++免费学习笔记(深入)”;
关键不是“存 PDF”,而是“建 CDF 累积数组”,然后用 std::lower_bound 快速定位区间:
std::vector<double> cdf_x = {/* sorted x_i */};
std::vector<double> cdf_acc = {/* cumulative sum of pdf_i * dx_i, normalized to 1 */};
<p>double sample_from_cdf(double u) {
auto it = std::lower_bound(cdf_acc.begin(), cdf_acc.end(), u);
size_t idx = std::max(it - cdf_acc.begin(), (long long)1) - 1;
double t = (u - cdf_acc[idx]) / (cdf_acc[idx+1] - cdf_acc[idx]);
return cdf_x[idx] + t * (cdf_x[idx+1] - cdf_x[idx]);
}- 查表前必须保证
cdf_x严格递增,且cdf_acc从 0 开始、以 1 结束 - 插值用
t而非固定步长,否则在 PDF 尖峰处会失真 - 表长建议 ≥ 1024:太短则逼近误差大;太长则 cache miss 显著,L1 miss 后延迟跳升 4–5 倍
拒绝采样要注意包络函数与接受率
对 PDF 形状复杂(如双峰、带截断)又难积分的情况,拒绝采样更鲁棒。但它性能取决于接受率——若包络函数 g(x) 远高于目标 f(x),大部分样本会被丢弃。
典型错误是直接用常数包络(如 max_pdf),在尾部区域浪费严重。更好的做法是用可快速采样的简单分布作包络,例如用指数分布包络带指数衰减的 PDF:
- 先采
x ~ Exp(lambda),再以概率f(x) / (M * g(x))接受(M是缩放因子,满足f(x) ≤ M * g(x)) -
M要尽可能接近 1:可通过数值优化(如 Brent 法)在关键区间搜最大比值 - 接受判断必须用
double比较,禁用float:PDF 计算中微小误差会导致接受率剧烈波动
实际部署时,建议先离线跑 10⁶ 次测接受率;低于 30% 就得重选包络或切回查表法。



















