在C/C++中按照正态分布生成随机数

Dam*_*ien 112 c c++ random distribution normal-distribution

如何在C或C++中正常分布后轻松生成随机数?

我不想使用Boost.

我知道Knuth详细谈论了这个问题,但我现在还没有他的书.

S.L*_*ott 91

有许多方法可以从常规RNG生成高斯分布数.

箱穆勒变换是常用的.它正确生成具有正态分布的值.数学很容易.您生成两个(统一)随机数,并通过对它们应用公式,您将获得两个正态分布的随机数.返回一个,并保存另一个以获取随机数的下一个请求.

  • 注意,C++ 11添加了[`std :: normal_distribution`](http://en.cppreference.com/w/cpp/numeric/random/normal_distribution),它完全符合您的要求,而无需深入研究数学细节. (24认同)
  • 如果你需要速度,那么极地方法会更快.Ziggurat算法甚至更多(尽管编写起来要复杂得多). (9认同)
  • std :: normal_distribution不保证在所有平台上保持一致.我现在正在进行测试,MSVC提供了一组不同的值,例如Clang.C++ 11引擎似乎生成相同的序列(给定相同的种子),但C++ 11发行版似乎是在不同平台上使用不同的算法实现的. (3认同)
  • 在这里找到了Ziggurat的一个实现http://people.sc.fsu.edu/~jburkardt/c_src/ziggurat/ziggurat.html它非常完整. (2认同)

Pet*_* G. 46

C++ 11

C++ 11提供的std::normal_distribution,这就是我今天要走的路.

C或更旧的C++

以下是按复杂程度递增的顺序解决方案:

  1. 从0到1添加12个均匀随机数并减去6.这将匹配正常变量的均值和标准差.一个明显的缺点是范围限制在±6 - 与真正的正态分布不同.

  2. Box-Muller变换.这在上面列出,并且实现起来相对简单.但是,如果您需要非常精确的样本,请注意Box-Muller变换与一些统一的生成器相结合会遭受称为Neave Effect 1的异常现象.

  3. 为了获得最佳精度,我建议绘制制服并应用反向累积正态分布来得到正态分布的变量.是一个非常好的逆累积正态分布算法.

1. HR Neave,"使用具有乘法同余伪随机数发生器的Box-Muller变换",Applied Statistics,22,92-97,1973

  • @stonybrooknick添加了原始参考.很酷的评论:谷歌搜索"box muller neave"找到参考,这个非常stackoverflow问题出现在第一个结果页面! (2认同)

Pau*_*l R 31

一种快速简便的方法是将一些均匀分布的随机数相加并取其平均值.有关其工作原理的完整说明,请参阅中心极限定理.

  • @Morlock平均值越接近高斯分布的样本数越大.如果您的应用程序对分发的准确性有严格的要求,那么您可能最好使用更严格的东西,比如Box-Muller,但对于许多应用程序,例如为音频应用程序产生白噪声,您可以获得相当少的数字平均样本(例如16). (4认同)
  • 另外,您如何对此进行参数化以获得一定的方差,比如说您希望平均值为10且标准偏差为1? (2认同)

Mil*_*Yip 24

为正态分布随机数生成基准创建了一个C++开源项目.

它比较了几种算法,包括

  • 中心极限定理方法
  • Box-Muller变换
  • Marsaglia极地方法
  • Ziggurat算法
  • 逆变换采样方法.
  • cpp11random使用C++ 11 std::normal_distributionstd::minstd_rand(它实际上是箱穆勒变换在铛).

floatiMac Corei5-3330S@2.70GHz上的单精度()版本的结果,clang 6.1,64位:

normaldistf

为了正确,程序验证样品的平均值,标准偏差,偏度和峰度.结果发现,通过求和4,8或16个均匀数的CLT方法与其他方法一样没有良好的峰度.

Ziggurat算法比其他算法具有更好的性能.但是,它不适合SIMD并行,因为它需要表查找和分支.具有SSE2/AVX指令集的Box-Muller比非SIMD版本的ziggurat算法快得多(x1.79,x2.99).

因此,我建议使用Box-Muller作为具有SIMD指令集的体系结构,否则可能是ziggurat.


PS基准测试使用最简单的LCG PRNG来生成均匀分布的随机数.因此对某些应用程序来说可能还不够.但性能比较应该是公平的,因为所有实现都使用相同的PRNG,因此基准测试主要测试转换的性能.

  • "但是性能比较应该是公平的,因为所有实现都使用相同的PRNG".除了BM每个输出使用一个输入RN,而CLT使用更多等等...所以生成统一随机的时间#重要. (2认同)

Pet*_*217 14

这是一个C++示例,基于一些参考.这很快又很脏,最好不要重新发明和使用boost库.

#include "math.h" // for RAND, and rand
double sampleNormal() {
    double u = ((double) rand() / (RAND_MAX)) * 2 - 1;
    double v = ((double) rand() / (RAND_MAX)) * 2 - 1;
    double r = u * u + v * v;
    if (r == 0 || r > 1) return sampleNormal();
    double c = sqrt(-2 * log(r) / r);
    return u * c;
}
Run Code Online (Sandbox Code Playgroud)

您可以使用QQ图来检查结果并查看其与实际正态分布的近似程度(对样本进行排序1..x,将等级转换为x总数的比例,即多少样本,得到z值并绘制它们.向上的直线是期望的结果).

  • FWIW,许多编译器将能够将特定的递归调用转换为“跳转到函数顶部”。问题是你是否想依赖它:-) 另外,它需要 > 10 次迭代的概率是 480 万分之一。p(>20) 是其平方,等等。 (2认同)

Joe*_*oeG 12

使用std::tr1::normal_distribution.

std :: tr1命名空间不是boost的一部分.它是包含C++技术报告1中的库添加的命名空间,可以在最新的Microsoft编译器和gcc中使用,与boost无关.

  • 他没有要求标准,他要求'不要提升'. (25认同)

Pet*_*ter 12

这是在现代C++编译器上生成示例的方法.

#include <random>
...
std::mt19937 generator;
double mean = 0.0;
double stddev  = 1.0;
std::normal_distribution<double> normal(mean, stddev);
cerr << "Normal: " << normal(generator) << endl;
Run Code Online (Sandbox Code Playgroud)

  • 它总是被播种的。有一个默认种子。 (2认同)

Den*_*aud 5

您可以使用GSL给出了一些完整的例子来演示如何使用它。