JavaScript Math.random 正态分布(高斯钟曲线)?

108

我想知道 JavaScript 函数 Math.random 是否使用正态分布而非均匀分布。

如果不是,我该如何获得使用正态分布的数字?在互联网上没有找到一个清晰的算法来生成随机正态分布的数字。

我想重建一台 Schmidt 机器(德国物理学家)。该机器会产生0或1的随机数,并且它们必须服从正态分布,以便我可以将它们作为高斯钟形曲线绘制出来。

例如,随机函数生成120个数(0或1),这些数值的平均值(均值)必须接近60。


1
不是答案(因为这不是你的问题:P),但这应该会有所帮助:http://www.meredithdodge.com/2012/05/30/a-great-little-javascript-function-for-generating-random-gaussiannormalbell-curve-numbers/ - Jongware
3
对 Math.random() 进行几次平均,即使只调用 3 次,也能得到类似正态分布的分布情况。请参见此处 https://jsbin.com/tetizotugu/1/edit?js,output,修改第一个参数即可。 - GameAlchemist
1
这是一个非常普遍的困惑,因为在人们理解正态曲线之前,统计学通常被教授为最重要的事情。如果您对每个样本取30次硬币的平均值(sum xs / length xs),那么这30个平均值将近似地服从于正态分布,并且随着您将30增加到更大的数字,它将收敛于以0.5为中心的完美正态曲线。随机本身是均匀整数分布(具有参数n、n + 1,通常为0、1),这就是在C++中使用的mersanne twister随机引擎mt19937中所知道的。 - Dmytro
2
这个问题本身就很有价值,因为它要求生成正态分布的随机数。关于 OP 的使用情况,他没有像 Dmirty 一样解释得很清楚。 - Andre Figueiredo
21个回答

3

我已经测试了几个函数,使用正确的配置后,它们都能够正常地工作。

http://jsfiddle.net/p3y40gf3/29/

中心极限定理很好,必须使用(n=3 for 6)和12 for 12才能看起来与其他内容相同。我还将其他内容配置为(6)或12或1/12作为标准差,不确定为什么是12。

中心极限定理比Box/Muller和Ziggurat略微偏离中心。

Box/Muller和Ziggurat看起来完全相同。

Joe(https://dev59.com/El8e5IYBdhLWcg3wlbTx#33567961)提供的这个变量正确地计算了标准差:

function normal(mu, sigma, nsamples){ // using central limit
    if(!nsamples) nsamples = 3
    if(!sigma) sigma = 1
    if(!mu) mu=0

    var run_total = 0
    for(var i=0 ; i<nsamples ; i++){
       run_total += Math.random()
    }

    return sigma*(run_total - nsamples/2)/(nsamples/2) + mu
}

Ziggurat也不错,但需要从z分数调整为从0到1,看起来可以产生好的数字。

Box/Muller剪裁法很好,但在剪裁边缘会产生很少重复的数字, 但它与其他方法非常相似, 不正确的随机数应该舍弃而不是剪裁。

function randn_bm() {
    var u = 0, v = 0;
    while(u === 0) u = Math.random(); //Converting [0,1) to (0,1)
    while(v === 0) v = Math.random();
    let num = Math.sqrt( -2.0 * Math.log( u ) ) * Math.cos( 2.0 * Math.PI * v );
    num = num / 6.0 + 0.5; // Translate to 0 -> 1 // changed here 10 to 6
    if(num>1||num<0) return randn_bm(); return num; // bad random numbers should be discared not clipped
    //return Math.max(Math.min(num, 1), 0); // cap between 0 and 1
}

中心极限变量称为贝茨分布,这是一个平均值。https://en.wikipedia.org/wiki/Bates_distribution 不要混淆与伊尔文哈尔分布,它是一个总和。https://en.wikipedia.org/wiki/Irwin%E2%80%93Hall_distribution

https://en.wikipedia.org/wiki/Normal_distribution#Generating_values_from_normal_distribution


3

skewnormal 通过 normalnormal01 实现

skewnormal(min, max, ..) 返回一个随机数,该随机数来自于正态分布,并被拉伸和偏移以使其范围从minmax,呈指数偏斜,且被截断为sigma个标准差(反向计算)。为了清晰易懂并可以直接从这些中间函数生成随机数,将其分解成逻辑步骤normalnormal01。(还有一个额外的lognormal!)

/// skewnormal(..) returns a random number from the normal distribution that has
/// been streched and offset to range from `min` to `max`, skewed with `skew`,
/// and truncated to `sigma` standard deviations. See https://dev59.com/El8e5IYBdhLWcg3wlbTx#74258559
const skewnormal = (min, max, skew = 1, sigma = 8) => {
  /// normal() returns a random number from the standard normal distribution.
  /// Uses the Box-Muller transform.
  const normal = () => Math.sqrt(-2.0 * Math.log(Math.random())) * Math.cos(2.0 * Math.PI * Math.random());

  /// normal01(..) returns normally distributed random number, whose range is
  /// truncated at `sigma` standard deviations and shifted to interval `[0, 1]`.
  const normal01 = (sigma) => {
    while (true) {
      let num = normal() / (sigma + 0.0) + 0.5; // translate to [0, 1]
      if (0 <= num && num <= 1) return num;     // ok if in range, else resample
    }
  }

  var num = normal01(sigma);
  num = Math.pow(num, skew) // skew
  num *= max - min // stretch to fill range
  num += min // offset to min
  return num;
}

/// lognormal() returns a random number from the log-normal distribution.
const lognormal = () => Math.exp(normal());

基于 joshuakcockrell 的另一个流行答案。您可能更喜欢这个实现,因为:1.它被分解为展示中间函数,2.它公开了在数学上相关和有用的 sigma 参数,3.它有更好的名称和注释。

查看 JSFiddle 完整演示环境,可以轻松定义、测试和可视化自己的随机分布函数,如下图所示:

上述分布函数的可视化

查看交互式图表:https://jsfiddle.net/rgefzusq/34/show/ Playground: https://jsfiddle.net/rgefzusq/34/


2

我之前写了一个非冗长函数,用于从高斯分布中随机抽样:

function gaussianRandom(mean, sigma) {
  let u = Math.random()*0.682;
  return ((u % 1e-8 > 5e-9 ? 1 : -1) * (Math.sqrt(-Math.log(Math.max(1e-9, u)))-0.618))*1.618 * sigma + mean;
}

如果您将值夹在所需范围内,它应该能够工作。


4
这个函数看起来像胸部http://jsfiddle.net/p3y40gf3/16/(它不是高斯形状),无论如何,它很有趣。 - Shimon Doodkin
4
@ShimonDoodkin 哎呀,你指出了一个错误,你是对的。我复制了一个修改过的函数版本。我现在已经更新为正确的版本,一切看起来都很好:http://jsfiddle.net/4334Lnh9/ - SuperEggbert
1
只是一个提示:夹取值是一个错误的想法,最好重新采样。否则,你会得到fat tails。 - Shimon Doodkin

1
这是我实现的JavaScript版本的算法P极坐标法生成正态分布随机数),取自Donald Knuth的书《计算机程序设计艺术》第3.4.1节:
function gaussian(mean, stddev) {
    return function() {
        var V1
        var V2
        var S
        do{
            var U1 = Math.random()
            var U2 = Math.random()
            V1 = 2*U1-1
            V2 = 2*U2-1
            S = V1*V1+V2*V2
        }while(S >= 1)
        if(S===0) return 0
        return mean+stddev*(V1*Math.sqrt(-2*Math.log(S)/S))
    }
} 

像这样使用:

var standard_normal = gaussian(0,1)
var a_standard_normal_deviate = standard_normal()

0
这是我使用Marsaglia极坐标法解决问题的方案。范围取决于您提供的参数,如果没有参数,则几乎不会生成任何超出范围的内容。
由于它每次迭代生成两个正态分布的数字,我声明了一个变量在window.temp.spareNormal下以获取多余的数字(如果有的话)。可能不是最好的位置,但嘿。
为了获得所需结果,您可能需要四舍五入。
window.temp = {
    spareNormal: undefined
};

Math.normal = function (mean, standardDeviation) {
    let q, u, v, p;

    mean = mean || 0.5;
    standardDeviation = standardDeviation || 0.125;

    if (typeof temp.spareNormal !== 'undefined') {
        v = mean + standardDeviation * temp.spareNormal;
        temp.spareNormal = undefined;

        return v;
    }

    do  {
        u = 2.0 * Math.random() - 1.0;
        v = 2.0 * Math.random() - 1.0;

        q = u * u + v * v;
    } while (q >= 1.0 || q === 0);

    p = Math.sqrt(-2.0 * Math.log(q) / q);

    temp.spareNormal = v * p;
    return mean + standardDeviation * u * p;
}

我会简化你的实现,并消除不靠谱的全局变量,因此:function getGaussianRandom(mean, standardDeviation) { return () => { let q, u, v, p; do { u = 2.0 * Math.random() - 1.0; v = 2.0 * Math.random() - 1.0; q = u * u + v * v; } while (q >= 1.0 || q === 0); p = Math.sqrt(-2.0 * Math.log(q) / q); return mean + standardDeviation * u * p; }; } - alecmce
注释会丢失换行符 :( - alecmce

0

以防万一:Math.pow(Math.random(), p)

例如:

function testR(max = 100, min = 0, p = 1, c = 20)
{
    let t = [];
  
    for (let i = 0; i < c; ++i)
    {
        t.push(Math.floor(Math.pow(Math.random(), p) * (max - min + 1) + min));
    }
  
    console.log(
        `p = ${String(p).padStart(5)}`, '|',
        t.sort(function (a, b) {  return a - b;  }).join(', ')
    );
}

testR(9, 0, 10);
testR(9, 0, 2);
testR(9, 0, 1);
testR(9, 0, 0.5);
testR(9, 0, 0.1);
testR(9, 0, 0.05);
Results in client/JS console

jsFiddle 图形测试:

graph


0

0

我唯一与此相关的资格是上过一门统计学课程。如果我有什么错误,请告诉我,我想更多地了解统计学,不想一直保持错误的想法。

如果你想创建一个产生正态分布数字的随机数生成器,你应该能够从均匀分布中取样,这没有问题。如果你设置一个基本的随机数生成器,它生成范围在a到b之间的数字,那么产生的值的分布将具有µ=(a+b)/2和σ=(b-a)/√12。如果从这个分布中取出几个样本值(≥30)的平均值,并对许多这样的样本进行采样,则对于抽样分布µ(样本均值)=µ(总体均值),σ(样本均值的标准差)=σ(总体标准差)/√n(样本中的值数)。

通过控制原始分布的均值和标准差,您可以控制产生正态分布的随机数生成器的最终均值和标准偏差。

function all_normal(mu, sigma, nsamp)
{
    var total = 0;
    for (var a = 0; a < nsamp; a ++)
    {
       total += rand_int(mu - (sigma * Math.sqrt(3 * nsamp)), mu + (sigma * Math.sqrt(3 * nsamp)));
    }
    return Math.ceil(total / nsamp);
}

0

寻找值的正态分布:

getNormal = (x, mean, standardDeviation, ) => {
 return (1 / standardDeviation * Math.sqrt(2 * (3, 14))) * Math.pow(Math.E, -Math.pow(x - mean, 2) / (2 * (standardDeviation * standardDeviation)));  
}

-1
let iset = 0;
let gset;

function randn() {

   let v1, v2, fac, rsq;

   if (iset == 0) {
   do {
     v1 = 2.0*Math.random() - 1.0;
     v2 = 2.0*Math.random() - 1.0;
     rsq = v1*v1+v2*v2;
   } while ((rsq >= 1.0) || (rsq == 0));
   fac = Math.sqrt(-2.0*Math.log(rsq)/rsq);
   gset = v1*fac;
   iset = 1;
   return v2*fac;
 } else {
   iset = 0;
   return gset;
 }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接