用C#实现Box-Mueller随机数发生器

时间:2011-04-28 10:57:22

标签: c# random non-uniform-distribution

this question: Random number generator which gravitates numbers to any given number in range?我做了一些研究,因为我之前遇到过这样一个随机数发生器。我记得的只是“穆勒”的名字,所以我想我找到了,在这里:

我可以在其他语言中找到它的大量实现,但我似乎无法在C#中正确实现它。

例如,此页面The Box-Muller Method for Generating Gaussian Random Numbers表示代码应如下所示(这不是C#):

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>

double gaussian(void)
{
   static double v, fac;
   static int phase = 0;
   double S, Z, U1, U2, u;

   if (phase)
      Z = v * fac;
   else
   {
      do
      {
         U1 = (double)rand() / RAND_MAX;
         U2 = (double)rand() / RAND_MAX;

         u = 2. * U1 - 1.;
         v = 2. * U2 - 1.;
         S = u * u + v * v;
      } while (S >= 1);

      fac = sqrt (-2. * log(S) / S);
      Z = u * fac;
   }

   phase = 1 - phase;

   return Z;
}

现在,这是我在C#中实现的上述内容。注意,变换产生2个数字,因此上面有“阶段”的技巧。我只是丢弃第二个值并返回第一个值。

public static double NextGaussianDouble(this Random r)
{
    double u, v, S;

    do
    {
        u = 2.0 * r.NextDouble() - 1.0;
        v = 2.0 * r.NextDouble() - 1.0;
        S = u * u + v * v;
    }
    while (S >= 1.0);

    double fac = Math.Sqrt(-2.0 * Math.Log(S) / S);
    return u * fac;
}

我的问题是以下特定情况,我的代码没有返回0-1范围内的值,我无法理解原始代码是如何的。

  • u = 0.5,v = 0.1
  • S变为0.5*0.5 + 0.1*0.1 = 0.26
  • fac变为〜3.22
  • 因此返回值为〜0.5 * 3.22或〜1.6

那不在0 .. 1范围内。

我做错了什么/不理解?

如果我修改我的代码,以便将facu相乘,而不是乘以S,我会得到一个范围从0到1的值,但它有错误分布(似乎最大分布在0.7-0.8左右,然后在两个方向逐渐减少。)

5 个答案:

答案 0 :(得分:9)

你的代码很好。你的错误是认为它应该只在[0, 1]内返回值。 (标准)正态分布是在整个实线上具有非零权重的分布。也就是说,[0, 1]之外的值是可能的。事实上,[-1, 0]中的值与[0, 1]中的值一样可能,而[0, 1]的补码约为正态分布权重的66%。因此,66%的时间我们期望值超出[0, 1]

另外,我认为这不是Box-Mueller变换,而实际上是Marsaglia极地方法。

答案 1 :(得分:2)

我不是数学家或统计学家,但如果我想到这一点,我就不会指望高斯分布在精确范围内返回数字。给定你的实现,平均值为0,标准偏差为1,所以我希望值在钟形曲线上分布,中心为0,然后随着数字偏离任何一侧的0而减小。所以序列肯定会涵盖两个+/-数字。

然后因为它是统计的,为什么仅仅因为std.dev是1而难以限制在-1..1?从统计上来说,任何一方都可以发挥作用,但仍然符合统计要求。

答案 2 :(得分:2)

均匀随机变量确实在0..1之内,但高斯随机变量(这是Box-Muller算法生成的)可以是实线上的任何位置。有关详细信息,请参阅wiki/NormalDistribution

答案 3 :(得分:1)

我认为该函数返回极坐标。因此,您需要两个值才能获得正确的结果。

此外,高斯分布不在0 .. 1之间。它最终可以很容易地达到1000,但这种情况发生的可能性非常低。

答案 4 :(得分:0)

这是一种蒙特卡罗方法,因此您无法限制结果,但您可以做的是忽略样本。

// return random value in the range [0,1].
double gaussian_random()
{
    double sigma = 1.0/8.0; // or whatever works.
    while ( 1 ) {
        double z = gaussian() * sigma + 0.5;
        if (z >= 0.0 && z <= 1.0)
            return z;
    }
}