在 C/C++ 中生成服从正态分布的随机数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2325472/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 23:03:09  来源:igfitidea点击:

Generate random numbers following a normal distribution in C/C++

c++crandomdistributionnormal-distribution

提问by Damien

How can I easily generate random numbers following a normal distribution in C or C++?

如何在 C 或 C++ 中按照正态分布轻松生成随机数?

I don't want any use of Boost.

我不想使用任何 Boost。

I know that Knuth talks about this at length but I don't have his books at hand right now.

我知道 Knuth 详细谈到了这个问题,但我现在手头没有他的书。

采纳答案by S.Lott

There are many methods to generate Gaussian-distributed numbers from a regular RNG.

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

The Box-Muller transformis commonly used. It correctly produces values with a normal distribution. The math is easy. You generate two (uniform) random numbers, and by applying an formula to them, you get two normally distributed random numbers. Return one, and save the other for the next request for a random number.

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

回答by Peter G.

C++11

C++11

C++11 offers std::normal_distribution, which is the way I would go today.

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

C or older C++

C 或更旧的 C++

Here are some solutions in order of ascending complexity:

以下是一些按复杂度升序排列的解决方案:

  1. Add 12 uniform random numbers from 0 to 1 and subtract 6. This will match mean and standard deviation of a normal variable. An obvious drawback is that the range is limited to ±6 – unlike a true normal distribution.

  2. The Box-Muller transform. This is listed above, and is relatively simple to implement. If you need very precise samples, however, be aware that the Box-Muller transform combined with some uniform generators suffers from an anomaly called Neave Effect1.

  3. For best precision, I suggest drawing uniforms and applying the inverse cumulative normal distribution to arrive at normally distributed variates. Hereis a very good algorithm for inverse cumulative normal distributions.

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

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

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

1. H. R. Neave, “On using the Box-Muller transformation with multiplicative congruential pseudorandom number generators,” Applied Statistics, 22, 92-97, 1973

1. HR Neave,“On using the Box-Muller transform with multiplicative congruential pseudorandom number generators”,Applied Statistics, 22, 92-97, 1973

回答by Paul R

A quick and easy method is just to sum a number of evenly distributed random numbers and take their average. See the Central Limit Theoremfor a full explanation of why this works.

一种快速简便的方法是将多个均匀分布的随机数相加并取其平均值。请参阅中心极限定理以获取有关其工作原理的完整解释。

回答by Milo Yip

I created a C++ open source project for normally distributed random number generation benchmark.

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

It compares several algorithms, including

它比较了几种算法,包括

  • Central limit theorem method
  • Box-Muller transform
  • Marsaglia polar method
  • Ziggurat algorithm
  • Inverse transform sampling method.
  • cpp11randomuses C++11 std::normal_distributionwith std::minstd_rand(it is actually Box-Muller transform in clang).
  • 中心极限定理方法
  • Box-Muller 变换
  • 马尔萨利亚极地法
  • Ziggurat算法
  • 逆变换采样方法。
  • cpp11random使用 C++11 std::normal_distributionwith std::minstd_rand(它实际上是 clang 中的 Box-Muller 变换)。

The results of single-precision (float) version on iMac [email protected] , clang 6.1, 64-bit:

floatiMac [email protected] , clang 6.1, 64-bit单精度 ( ) 版本的结果:

normaldistf

正态分布

For correctness, the program verifies the mean, standard deviation, skewness and kurtosis of the samples. It was found that CLT method by summing 4, 8 or 16 uniform numbers do not have good kurtosis as the other methods.

为了正确性,程序会验证样本的均值、标准偏差、偏度和峰度。发现将 4、8 或 16 个均匀数相加的 CLT 方法没有其他方法那样好的峰度。

Ziggurat algorithm has better performance than the others. However, it does not suitable for SIMD parallelism as it needs table lookup and branches. Box-Muller with SSE2/AVX instruction set is much faster (x1.79, x2.99) than non-SIMD version of ziggurat algorithm.

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

Therefore, I will suggest using Box-Muller for architecture with SIMD instruction sets, and may be ziggurat otherwise.

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



P.S. the benchmark uses a simplest LCG PRNG for generating uniform distributed random numbers. So it may not be sufficient for some applications. But the performance comparison should be fair because all implementations uses the same PRNG, so the benchmark mainly tests the performance of the transformation.

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

回答by Pete855217

Here's a C++ example, based on some of the references. This is quick and dirty, you are better off not re-inventing and using the boost library.

这是一个基于一些参考资料的 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;
}

You can use a Q-Q plot to examine the results and see how well it approximates a real normal distribution (rank your samples 1..x, turn the ranks into proportions of total count of x ie. how many samples, get the z-values and plot them. An upwards straight line is the desired result).

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

回答by Petter

This is how you generate the samples on a modern C++ compiler.

这就是在现代 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;

回答by JoeG

Use std::tr1::normal_distribution.

使用std::tr1::normal_distribution.

The std::tr1 namespace is not a part of boost. It's the namespace that contains the library additions from the C++ Technical Report 1 and is available in up to date Microsoft compilers and gcc, independently of boost.

std::tr1 命名空间不是 boost 的一部分。它是包含 C++ 技术报告 1 中的库添加项的命名空间,并且在最新的 Microsoft 编译器和 gcc 中可用,独立于 boost。

回答by Denis Arnaud

You can use the GSL. Some complete examples are givento demonstrate how to use it.

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

回答by telcom

Have a look on: http://www.cplusplus.com/reference/random/normal_distribution/. It's the simplest way to produce normal distributions.

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

回答by Drew Noakes

If you're using C++11, you can use std::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);

There are many other distributions you can use to transform the output of the random number engine.

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