处理正态(高斯)分布

Dealing with normal (Gaussian) distribution

我基本上停留在一个相当简单的问题上:

Toss N coins and discover, how many of them land heads

解决方案性能不能依赖于 N,所以我们不能只调用 Math.random() < 0.5 N 次。显然,高斯分布可以解决问题。

我用的是 Box-Muller 方法:

function gaussian_random(mean, variance) {
  var s;
  var x;
  var y;
  do {
    x = Math.random() * 2.0 - 1.0;
    y = Math.random() * 2.0 - 1.0;
    s = Math.pow(x, 2) + Math.pow(y, 2);
  } while ( (s > 1) || (s == 0) );

  var gaussian = x * Math.sqrt(-2*Math.log(s)/s);
  return mean + gaussian * Math.sqrt(variance);
}

数学表明,N 次抛硬币的 均值 N/2 并且 方差 N/4

然后,我做了测试,抛N个硬币M次,给出正面朝上的最小数、最大数和平均数

我比较了朴素方法(Math.random() 多次)和 Gaussian Box-Muller 方法的结果。

有典型的测试输出:

Toss 1000 coins, 10000 times
Straight method: 
Elapsed time: 127.330 ms
Minimum: 434
Maximum: 558
Average: 500.0306
Box-Muller method: 
Elapsed time: 2.575 ms
Minimum: 438.0112461962819
Maximum: 562.9739632480057
Average: 499.96195358695064

Toss 10 coins, 10000 times
Straight method: 
Elapsed time: 2.100 ms
Minimum: 0
Maximum: 10
Average: 5.024
Box-Muller method: 
Elapsed time: 2.270 ms
Minimum: -1.1728354576573263
Maximum: 11.169478925333504
Average: 5.010078819562535

正如我们在 N = 1000 上看到的那样,它几乎完美契合。

但是,在 N = 10 上,Box-Muller 发疯了:它允许这样的测试结果,我可以(很少,但有可能)从 10 次抛硬币中得到 11.17 次正面朝上! :)

毫无疑问,我做错了什么。但具体是什么?

source of test, and link to launch it

Updated x2: 看来,之前我没有把问题描述好。有通用版:

How to get sample mean of N uniform random values (either discrete or continuous) in amortized constant time. Gaussian distribution is efficient for large N, but is there a way to make it work good on small N? Or on which exactly N, solution should switch from Gaussian method to some other (for exampled straightforward).

Math says, that mean of N coin tosses is N/2 and variance is N/4.

数学只说如果它是一个公平的硬币。而且解决方案 不可能依赖于 N.

假设所有的投掷都是相互独立的,对于精确的行为使用二项分布而不是正态分布。二项式有两个参数:N 是抛硬币的次数,p 是正面朝上(或者反面朝上)的概率。在伪代码中...

function binomial(n, p) {
  counter = 0
  successes = 0
  while counter < n {
    if Math.random() <= p
       successes += 1
    counter += 1
  }
  return successes
}

对于大 N 有更快的算法,但这很简单并且在数学上是正确的。

根据 中讨论的内容,我想出了这个特定的解决方案。

有经验法则n*p >= 10 and n*(1-p) >= 10,但让我们定义更严格的规则。

首先,Box-Muller 将严格限制在 [-6,6],以确保正确的结果(640 kB 应该...,我的意思是,6 西格玛应该足够了给大家).

然后,使用 6 常数,我们声明,为了使 Box-Muller 产生有效结果,Math.sqrt(variance) * 6 不得超过 mean。在将 N/2N/4 分别用作 meanvariance 以及归约后,我们最终得到:

Math.sqrt(N) * 6 <= N

N >= 36

虽然这个条件成立,但我们可以安全地使用封顶 Box-Muller 高斯。 对于任何较小的样本量,坚持二项式解决方案。

刚刚从统计上检查了这条规则 - 在相对大量(1000 万)的测试中,最小值不再超出样本量 32 及以上的边界。