Answers:
有许多方法可以从常规RNG生成高斯分布数。
该箱穆勒变换是常用的。它会正确产生具有正态分布的值。数学很简单。您生成两个(均匀)随机数,然后对它们应用公式,就得到两个正态分布的随机数。返回一个,并将另一个保存为下一个随机数请求。
std::normal_distribution
功能,而无需深入研究数学细节。
C ++ 11提供了std::normal_distribution
,这就是我今天要去的方式。
以下是一些按升序排列的解决方案:
将0到1之间的12个均匀随机数相加并减去6。这将与正常变量的均值和标准差相匹配。一个明显的缺点是范围限制为±6,这与真实的正态分布不同。
Box-Muller变换。这已在上面列出,并且实现起来相对简单。但是,如果您需要非常精确的样本,请注意,将Box-Muller变换与某些均匀生成器结合使用会遇到一个称为Neave Effect 1的异常现象。
为了获得最佳精度,我建议绘制制服并应用逆累积正态分布以得出正态分布变量。这是逆累积正态分布的很好算法。
1. HR Neave,“关于将Box-Muller变换与乘法同余伪随机数生成器一起使用”,《应用统计》,1973年第22、92-97页
一种快速简便的方法是将多个均匀分布的随机数求和并取其平均值。有关为何有效的完整说明,请参见中心极限定理。
我为正态分布的随机数生成基准创建了一个C ++开源项目。
它比较了几种算法,包括
cpp11random
std::normal_distribution
与C ++ 11 配合使用std::minstd_rand
(实际上是clang中的Box-Muller转换)。float
在iMac Corei5-3330S @ 2.70GHz,clang 6.1、64位上的单精度()版本的结果:
为了正确起见,程序将验证样本的平均值,标准偏差,偏度和峰度。已经发现,通过将4、8或16个统一数相加得到的CLT方法不具有其他方法的峰度。
Ziggurat算法具有比其他算法更好的性能。但是,它不适合SIMD并行性,因为它需要表查找和分支。具有SSE2 / AVX指令集的Box-Muller比ziggurat算法的非SIMD版本要快得多(x1.79,x2.99)。
因此,我建议将Box-Muller用于具有SIMD指令集的体系结构,否则可能是之字形。
PS基准测试使用最简单的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;
}
您可以使用QQ图来检查结果,并查看其与真实正态分布的近似程度(对样本1..x进行排名,将等级转换为x总数的比例,即有多少样本获得z值并绘制它们。向上的直线是理想的结果)。
使用std::tr1::normal_distribution
。
std :: tr1命名空间不是boost的一部分。它是包含C ++技术报告1中的库添加内容的名称空间,并且可以独立于boost使用最新的Microsoft编译器和gcc。
看一下:http : //www.cplusplus.com/reference/random/normal_distribution/。这是产生正态分布的最简单方法。
如果您使用的是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.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);
}
这可能不是最好的方法,但是很简单。
rand()
of RANDU
返回零,则宏将失败,因为Ln(0)未定义。
cos(2*pi*rand/RAND_MAX)
,而与相乘(rand()%2 ? -1.0 : 1.0)
。
该comp.lang.c常见问题列表股三种不同的方式,容易产生高斯分布随机数。
您可以看一下:http : //c-faq.com/lib/gaussian.html
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;
}
存在用于逆累积正态分布的各种算法。在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方法应用于不良线性同余生成器中的数字,则转换后的数字将提供极差的空间覆盖率。在许多书中都可以找到带有螺旋尾巴的变换后的数字图,最著名的是里普利的经典书,他可能是第一位进行此观察的人。”
1)使用类似于蒙特卡洛方法的方法,可以直观地生成高斯随机数。您将使用C中的伪随机数生成器在高斯曲线周围的框中生成一个随机点。您可以使用分布方程式计算该点是在高斯分布内部还是下方。如果该点在高斯分布内,那么您将获得高斯随机数作为该点的x值。
这种方法并不完美,因为从技术上讲,高斯曲线会朝着无穷大方向前进,并且您无法创建一个在x维度上接近无穷大的框。但是,高斯曲线在y维度上非常快地接近0,因此我不必为此担心。C语言中变量大小的约束可能更多地限制了您的准确性。
2)另一种方法是使用中央极限定理,该定理指出当添加独立随机变量时,它们形成正态分布。牢记这个定理,您可以通过添加大量独立的随机变量来近似高斯随机数。
这些方法不是最实用的方法,但是当您不想使用预先存在的库时,这是可以预期的。请记住,这个答案来自没有或没有微积分或统计经验的人。
计算机是确定性设备。计算中没有随机性。此外,CPU中的算术设备可以评估一些有限的整数集(在有限域中执行评估)和有限的实有理数集。并且还执行了按位运算。数学可以处理更多无穷大的集合,例如[0.0,1.0]。
您可以使用某些控制器收听计算机内部的某些电线,但是它的分布均匀吗?我不知道。但是,如果假设信号是累积大量独立随机变量值的结果,那么您将收到近似正态分布的随机变量(概率论中已证明)
存在称为“伪随机发生器”的算法。如我所见,伪随机发生器的目的是模拟随机性。Goodnes的标准是:-经验分布已收敛(从某种意义上说是逐点均匀L2)到理论上-从随机生成器收到的值似乎是独立的。从“真实的观点”来看,这当然是不正确的,但我们认为这是正确的。
一种流行的方法-您可以求和具有均匀分布的12个irv ....但是老实说,在使用傅立叶变换,泰勒级数进行推导中心极限定理时,它需要两次n-> + inf个假设。因此,例如从理论上讲-就我个人而言,我不理解人们如何以均匀分布执行12 irv的求和。
我在大学里有能力理论。对我来说,这尤其是一个数学问题。在大学里,我看到了以下模型:
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;
}
这样的方式只是一个例子,我想它是实现它的另一种方式。
证明正确的证据可以在克里希琴科·亚历山大·彼得罗维奇( Krishchenko Alexander Petrovich)的书“莫斯科,BMSTU,2004年:XVI概率论,示例6.12,第246-247页”中找到,ISBN 5-7038-2485-0
不幸的是,我不知道这本书有没有翻译成英文。