如何在C或C ++中轻松生成正态分布的随机数?
我不想使用Boost。
我知道Knuth对此进行了详细讨论,但我现在手头没有他的书。
如何在C或C ++中轻松生成正态分布的随机数?
我不想使用Boost。
我知道Knuth对此进行了详细讨论,但我现在手头没有他的书。
有许多方法可以从常规随机数生成器中生成高斯分布的数字。
Box-Muller变换是常用的方法。它可以正确地产生具有正态分布的值。这个数学公式很简单。您可以生成两个(均匀)随机数,通过对它们应用一个公式,您可以获得两个正态分布的随机数。返回一个,将另一个保存下来以供下一次请求随机数时使用。
C++11提供了std::normal_distribution
,这是我今天会选择的方法。
以下是按升序排列的一些解决方案:
将0到1之间的12个均匀随机数相加并减去6。这将匹配正态变量的平均值和标准差。一个明显的缺点是范围仅限于±6,而不像真正的正态分布。
Box-Muller变换。这在上面已经列出,并且相对容易实现。然而,如果需要非常精确的样本,请注意Box-Muller变换与一些均匀生成器结合使用会受到称为Neave Effect1的异常影响。
为了获得最佳精度,建议绘制均匀分布并应用逆累积正态分布来获得正态分布变量。这里有一个非常好的逆累积正态分布算法。
1. H. R. Neave, “On using the Box-Muller transformation with multiplicative congruential pseudorandom number generators,” Applied Statistics, 22, 92-97, 1973
一种快速而简单的方法是将一些均匀分布的随机数相加,并取它们的平均值。请参阅中心极限定理,了解这种方法有效的完整解释。
我为正态分布随机数生成创建了一个C++开源项目,它比较了几种算法,包括:
cpp11random
使用C++11 std::normal_distribution
与std::minstd_rand
(实际上在clang中是Box-Muller变换)。在iMac Corei5-3330S@2.70GHz , clang 6.1,64位单精度(float
)版本的结果如下:
为了检验正确性,程序验证了样本的平均值、标准差、偏度和峰度。发现通过对4个、8个或16个均匀数求和的CLT方法在峰度方面不如其他方法表现好。
Ziggurat算法的性能比其他方法更好。然而,它不适用于SIMD并行处理,因为需要进行表查找和分支操作。使用带有SIMD指令集的Box-Muller比非SIMD版本的ziggurat算法快得多(x1.79,x2.99)。
因此,我建议在具有SIMD指令集的架构中使用Box-Muller,否则可以考虑使用ziggurat。
P.S.基准测试使用最简单的LCG PRNG生成均匀分布的随机数。因此,对于某些应用程序而言,这可能不足够。但是,由于所有实现都使用相同的PRNG,因此性能比较应该是公平的,因此基准测试主要测试了变换的性能。
这是一个基于一些参考文献的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;
}
您可以使用Q-Q图来检查结果,看它如何逼近真正的正态分布(将样本排名为1..x,将排名变成总x计数的比例即样本数量,获取z值并将其绘制出来。向上的直线是期望的结果)。这是现代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;
使用std::tr1::normal_distribution
。
std::tr1
命名空间不是boost的一部分。它包含来自C++技术报告1的库添加,并可在最新的Microsoft编译器和gcc中独立于boost使用。
如果你使用的是C++11,你可以使用std::normal_distribution
:
#include <random>
std::default_random_engine generator;
std::normal_distribution<double> distribution(/*mean=*/0.0, /*stddev=*/1.0);
double randomNumber = distribution(generator);
除了这个随机数引擎,还有许多其他发行版可以用来转换其输出。
请看:http://www.cplusplus.com/reference/random/normal_distribution/。这是生成正态分布的最简单方法。