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

时间:2010-02-24 11:16:03

标签: c++ c random distribution normal-distribution

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

我不想使用Boost。

我知道Knuth详细讨论了这个问题,但我现在手边没有他的书。

18 个答案:

答案 0 :(得分:89)

generate Gaussian-distributed numbers from a regular RNG有很多方法。

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

答案 1 :(得分: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. 为了获得最佳精确度,我建议绘制制服并应用反向累积正态分布来得到正态分布的变量。 Here是反向累积正态分布的一种非常好的算法。

  4. 1。 H. R. Neave,“关于使用具有乘法同余伪随机数发生器的Box-Muller变换”,Applied Statistics,22,92-97,1973

答案 2 :(得分:31)

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

答案 3 :(得分:23)

我创建了C++ open source project for normally distributed random number generation benchmark

它比较了几种算法,包括

  • 中心极限定理方法
  • Box-Muller转换
  • Marsaglia极地方法
  • Ziggurat算法
  • 逆变换采样方法。
  • cpp11random使用C ++ 11 std::normal_distributionstd::minstd_rand(实际上是clang中的Box-Muller变换)。

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

normaldistf

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

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

因此,我建议将Box-Muller用于具有SIMD指令集的架构,否则可能是Ziggurat。

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

答案 4 :(得分:14)

这是一个C ++示例,基于一些参考。这很快就很脏,你最好不要重新发明和使用升级库。

#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;
}

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

答案 5 :(得分:12)

使用std::tr1::normal_distribution

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

答案 6 :(得分: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;

答案 7 :(得分:4)

如果您使用的是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);

您可以使用许多其他发行版来转换随机数引擎的输出。

答案 8 :(得分:4)

查看:http://www.cplusplus.com/reference/random/normal_distribution/。这是产生正态分布的最简单方法。

答案 9 :(得分:4)

您可以使用GSL。一些complete examples are given来演示如何使用它。

答案 10 :(得分:3)

我已按照http://www.mathworks.com/help/stats/normal-distribution.html中给出的PDF定义,并提出了这个问题:

const double DBL_EPS_COMP = 1 - DBL_EPSILON; // DBL_EPSILON is defined in <limits.h>.
inline double RandU() {
    return DBL_EPSILON + ((double) rand()/RAND_MAX);
}
inline double RandN2(double mu, double sigma) {
    return mu + (rand()%2 ? -1.0 : 1.0)*sigma*pow(-log(DBL_EPS_COMP*RandU()), 0.5);
}
inline double RandN() {
    return RandN2(0, 1.0);
}

这可能不是最好的方法,但它很简单。

答案 11 :(得分:1)

存在用于逆累积正态分布的各种算法。量化金融中最受欢迎的是http://chasethedevil.github.io/post/monte-carlo--inverse-cumulative-normal-distribution/

在我看来,除了Wichura的算法AS241之外,没有太多动机使用其他东西:机器精度高,可靠且快速。高斯随机数生成中很少出现瓶颈。

此外,它显示了Ziggurat之类的方法的缺点。

这里的最佳答案主张Box-Müller,你应该知道它已经知道缺陷。我引用https://www.sciencedirect.com/science/article/pii/S0895717710005935

  在文献中,Box-Muller有时被认为略逊一筹,主要有两个原因。首先,如果将Box-Muller方法应用于来自不良线性同余生成器的数字,则转换后的数字会提供极差的空间覆盖。在许多书中都可以找到带有螺旋尾巴的变形数字图,最值得注意的是里普利的经典着作,他可能是第一个做出这种观察的书。“

答案 12 :(得分:1)

Box-Muller实施:

#include <cstdlib>
#include <cmath>
#include <ctime>
#include <iostream>
using namespace std;
 // return a uniformly distributed random number
double RandomGenerator()
{
  return ( (double)(rand()) + 1. )/( (double)(RAND_MAX) + 1. );
}
 // return a normally distributed random number
double normalRandom()
{
  double y1=RandomGenerator();
  double y2=RandomGenerator();
  return cos(2*3.14*y2)*sqrt(-2.*log(y1));
}

int main(){
double sigma = 82.;
double Mi = 40.;
  for(int i=0;i<100;i++){
double x = normalRandom()*sigma+Mi;
    cout << " x = " << x << endl;
  }
  return 0;
}

答案 13 :(得分:0)

comp.lang.c常见问题列表分享了三种不同的方法来轻松生成具有高斯分布的随机数。

您可以看一下:http://c-faq.com/lib/gaussian.html

答案 14 :(得分:0)

1)使用类似于蒙特卡洛方法的方法,可以直观地生成高斯随机数。您可以使用C中的伪随机数生成器在高斯曲线周围的框中生成一个随机点。您可以使用分布方程式来计算该点是在高斯分布的内部还是下方。如果该点在高斯分布内,那么您就获得了高斯随机数作为该点的x值。

此方法并不完美,因为从技术上讲,高斯曲线会朝着无穷大方向前进,并且您无法创建一个在x维度上接近无穷大的框。但是,高斯曲线在y维度上非常快地接近0,因此我不必为此担心。 C语言中变量大小的限制可能更多地限制了您的准确性。

2)另一种方法是使用中央极限定理,该定理指出当添加独立随机变量时,它们形成正态分布。牢记这个定理,您可以通过添加大量独立的随机变量来近似高斯随机数。

这些方法不是最实用的方法,但是当您不想使用预先存在的库时,这是可以预期的。请记住,这个答案来自没有或没有微积分或统计经验的人。

答案 15 :(得分:0)

蒙特卡罗方法 最直观的方法是使用蒙特卡洛方法。取一个合适的范围-X,+ X。 X的值越大,将导致更准确的正态分布,但收敛时间越长。 一种。在-X到X之间选择一个随机数 z 。 b。保持N(z, mean, variance)的概率,其中N是高斯分布。否则放回步骤(a)。

答案 16 :(得分:-1)

看看我发现了什么。

library使用Ziggurat算法。

答案 17 :(得分:-3)

计算机是确定性设备。计算中没有随机性。 此外,CPU中的运算装置可以在一些有限的整数数组(在有限域中执行评估)和有限的实数有理集上求和。并且还执行按位操作。数学与[0.0,1.0]这样的更好的集合达成协议,并且点数无限。

你可以用一些控制器听一些计算机内部的电线,但是它会有均匀的分布吗?我不知道。但如果假设它的信号是累积值大量独立随机变量的结果那么你会得到近似正态的分布随机变量(在概率论中得到证明)

存在称为伪随机生成器的算法。当我试探伪随机生成器的目的是模仿随机性。而goodnes的标准是: - 经验分布从理论上收敛(在某种意义上 - 逐点,统一,L2) - 您从随机生成器收到的值似乎是相互依赖的。当然,从“真实的观点”来看并不是这样,但我们认为这是真的。

一种流行的方法 - 你可以将12个irv与均匀分布相加......但是在推导中心极限定理的过程中,在傅里叶变换,泰勒级数的帮助下,它需要有n-&gt; + inf。几次假设。 所以例如理论上 - 我个人并不知道人们如何执行12 i.r.v的汇总。分布均匀。

我在大学里有可操作性理论。对我而言,这只是一个数学问题。在大学里,我看到了以下模型:

double generateUniform(double a, double b)
{
  return uniformGen.generateReal(a, b);
}

double generateRelei(double sigma)
{
  return sigma * sqrt(-2 * log(1.0 - uniformGen.generateReal(0.0, 1.0 -kEps)));
}
double generateNorm(double m, double sigma)
{
  double y2 = generateUniform(0.0, 2 * kPi);
  double y1 = generateRelei(1.0);
  double x1 = y1 * cos(y2);
  return sigma*x1 + m;
}

这样的方式只是一个例子,我想它存在另一种实现方式。

在本书中可以找到正确的证据 “莫斯科,BMSTU,2004:XVI概率论,例6.12,第246-247页”Krishchenko Alexander Petrovich ISBN 5-7038-2485-0

不幸的是,我不知道这本书的翻译是否存在英文。