107

I want to know if the JavaScript function Math.random uses a normal (vs. uniform) distribution or not.

If not, how can I get numbers which use a normal distribution? I haven't found a clear answer on the Internet, for an algorithm to create random normally-distributed numbers.

I want to rebuild a Schmidt-machine (German physicist). The machine produces random numbers of 0 or 1, and they have to be normally-distributed so that I can draw them as a Gaussian bell curve.

For example, the random function produces 120 numbers (0 or 1) and the average (mean) of these summed values has to be near 60.

Beejor
  • 8,606
  • 1
  • 41
  • 31
Mangooxx
  • 1,313
  • 2
  • 12
  • 13
  • 1
    Not an answer (because it wasn't your question :P), but this should help: http://www.meredithdodge.com/2012/05/30/a-great-little-javascript-function-for-generating-random-gaussiannormalbell-curve-numbers/ – Jongware Aug 30 '14 at 13:35
  • 3
    averaging a few Math.random() already gives you a normal-ish distribution with even a little few calls like 3. see here https://jsbin.com/tetizotugu/1/edit?js,output modify the first parameter. – GameAlchemist Feb 18 '16 at 00:19
  • 1
    this is a very common confusion, since statistics is taught as if the normal curve is the most important thing before people can understand what it is. If you were to take 30 averages(sum xs / length xs) of 30 coins per sample. these 30 averages would be approximately normally distributed, and as you keep increasing 30 to larger numbers, it will converge towards perfect normal curve centered around 0.5. random itself is uniform int distribution(with parameters n, n + 1 usually 0, 1), which is how it's known as in mersanne twister random engine mt19937 in c++. – Dmytro Aug 11 '17 at 17:12
  • 2
    The question itself is valuable, as asking for normal dist random numbers. About OP use case, he was not able to explain it very well as Dmirty.. – Andre Figueiredo Jan 18 '18 at 13:44

21 Answers21

191

Since this is the first Google result for "js gaussian random" in my experience, I feel an obligation to give an actual answer to that query.

The Box-Muller transform converts two independent uniform variates on (0, 1) into two standard Gaussian variates (mean 0, variance 1). This probably isn't very performant because of the sqrt, log, and cos calls, but this method is superior to the central limit theorem approaches (summing N uniform variates) because it doesn't restrict the output to the bounded range (-N/2, N/2). It's also really simple:

// Standard Normal variate using Box-Muller transform.
function gaussianRandom(mean=0, stdev=1) {
    const u = 1 - Math.random(); // Converting [0,1) to (0,1]
    const v = Math.random();
    const z = Math.sqrt( -2.0 * Math.log( u ) ) * Math.cos( 2.0 * Math.PI * v );
    // Transform to the desired mean and standard deviation:
    return z * stdev + mean;
}
Philippe Fanaro
  • 6,148
  • 6
  • 38
  • 76
Maxwell Collard
  • 2,587
  • 3
  • 16
  • 12
  • 3
    On 1000 samples, the range is `[-3.10, 3.24]` https://gist.github.com/Dorian/919f9ad749f28e5b57e655919d7eebb9 – Dorian Aug 28 '16 at 00:35
  • 4
    @Dorian Not altogether unexpected, given that the probability of a normal variate being outside of that range is about one in 1000: `$ python3`; `>>> import scipy.stats as stats`; `>>> 1 - ( stats.norm.cdf( 3.24 ) - stats.norm.cdf( -3.10 ) )`; `0.0015652517111527375` – Maxwell Collard Aug 29 '16 at 16:07
  • 2
    @Dorian: Thanks, I ended up using an approximate gaussian function that has a range of `[0, 1]` as needed in my program: http://stackoverflow.com/a/39187274/407213. – Dorian Aug 29 '16 at 17:04
  • In the past I have used the Polar form (as illustrated in Jóhann's answer), but wanted to see the performance difference between Basic and Polar forms in JavaScript. [The results](https://jsperf.com/box-mullerperformance) indicate that the Polar form is faster (as stated [by others](https://www.taygeta.com/random/gaussian.html)). So keep this in mind if performance is your top priority. – ssell Feb 23 '17 at 06:50
  • 1
    Why do you need to exclude 0 when the odds of 0 coming up are less than 1 in 10^300? – RamenChef Jan 26 '18 at 18:49
  • @RamenChef you need to exclude 0 because otherwise the function will fail sometimes at random. The fact that it is unlikely is no reason to write a function that can fail in normal use, especially when the fix is so trivial. Additionally, at double precision there are far fewer than 10^300 values on the interval [0,1), in fact there are only about 2^62 or roughly 10^18, so it will fail 10^282 times as often as you thought: https://stackoverflow.com/questions/5350227/what-is-the-total-number-of-unique-values-for-a-double-in-the-range-0-0-1-0 – user2711915 Mar 29 '19 at 19:00
  • @user2711915 I’m not going to argue with the rest of your comment, but those 10^62 double precision values are not distributed evenly within the interval: the difference between 0 and the next lowest double value is less than 10^300. – RamenChef Mar 30 '19 at 16:53
  • Agreed, but not relevant to the probability of a Uniform RNG on the interval [0, 1) selecting the value 0: If there are only N possible values on that interval (here N = 2^62 - note _not_ 10^62), then the Uniform RNG *either* has chance of 1/N of landing on zero *or* if fewer values are used to ensure better uniformity then it has a chance higher than 1/N. Hence the 1 in 10^18 probability I gave above was a lower bound for how often it could fail and already so vastly above your original claim that I felt there was no need to labour the point further. – user2711915 Apr 01 '19 at 13:19
  • 1
    Isn't it only `u` that needs to be in the range `(0,1)`, whereas `v` could be in the full range `[0,1)`? – Jack Aidley Jul 19 '19 at 11:41
  • 10
    Why not just set `u = 1 - Math.random()` and forget the while loop condition? – Vortico Mar 24 '20 at 12:54
  • 1
    @Vortico The Math.random() function returns a floating-point, pseudo-random number in the range 0 to less than 1 (inclusive of 0, but not 1). So when Math.random() returns 0, v will be 1. This will make our values in (0,1]. – Indra Dec 17 '20 at 12:21
  • 7
    @Indra Yes, and that's not a problem. In fact, we can forget the while loop for `v` and write the function in a slightly more performant one-liner. `return Math.sqrt(-2 * Math.log(1 - Math.random())) * Math.cos(2 * Math.PI * Math.random())` – Vortico Dec 18 '20 at 12:45
  • Is there any way to make this function sample from normal distribution given mean and variance (not just 0 and 1)? – Artod Jan 05 '22 at 22:17
  • 2
    @Artod probably just multiply by mean and divide be variance – Artod Jan 05 '22 at 22:19
  • @Vortico great suggestions for removing the while loops, I edited the answer to remove them. – paulkernfeld Aug 19 '22 at 13:55
  • 1
    The comment in the code should say `//Converting [0,1) to (0,1]` instead of `//Converting [0,1) to (0,1)` – Carlos Pinzón Nov 01 '22 at 10:19
  • For those following along at home, my original post from years ago erroneously stated that Box-Muller worked on (0, 1] for U and V, and so inverted both to be compatible for the spec for `Math.random()`. From what I can tell this is not true, and U and V need to be in (0, 1); there was an intermediate edit from a while ago that fixed this with some quick `while` loops, but this edit was changed to the current version, which has comments that are reflective of the earlier scheme. I think the version that used loops to make the variates on (0, 1) is more formally correct, if less performant. – Maxwell Collard Nov 29 '22 at 23:53
166

Normal Distribution Between 0 and 1

Building on Maxwell's Answer, this code uses the Box–Muller transform to give you a normal distribution between 0 and 1 inclusive. It just resamples the values if it's more than 3.6 standard deviations away (less than 0.02% chance).

function randn_bm() {
  let 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 / 10.0 + 0.5; // Translate to 0 -> 1
  if (num > 1 || num < 0) return randn_bm() // resample between 0 and 1
  return num
}

Visualizations

enter image description here

n = 100

enter image description here

n = 10,000

enter image description here

n = 10,000,000

Normal Distribution With Min, Max, Skew

This version allows you to give a min, max, and skew factor. See my usage examples at the bottom.

function randn_bm(min, max, skew) {
  let 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 / 10.0 + 0.5 // Translate to 0 -> 1
  if (num > 1 || num < 0) 
    num = randn_bm(min, max, skew) // resample between 0 and 1 if out of range
  
  else{
    num = Math.pow(num, skew) // Skew
    num *= max - min // Stretch to fill range
    num += min // offset to min
  }
  return num
}

enter image description here

randn_bm(-500, 1000, 1);

enter image description here

randn_bm(10, 20, 0.25);

enter image description here

randn_bm(10, 20, 3);

Here is the JSFiddle for these screenshots: https://jsfiddle.net/2uc346hp/

vsync
  • 118,978
  • 58
  • 307
  • 400
joshuakcockrell
  • 5,200
  • 2
  • 34
  • 47
  • 2
    What exactly the skew factor represents? It doesn't seems to represent standard deviation or variance. How can I understand it mathematically? –  Dec 16 '19 at 17:41
  • 2
    @André I admit, there's not much mathematical backing behind the skew factor in my answer here. It was just a convenient way of stretching or shrinking the data for my use case. If you want to implement a legit skew-normal transform with proper mean, standard deviation, and shape variables, you could check out this link. https://spin.atomicobject.com/2019/09/30/skew-normal-prng-javascript/ – joshuakcockrell Dec 17 '19 at 01:26
  • Also, here is my original JS Fiddle for this answer https://jsfiddle.net/ktq9jaoe/4/ Maybe I will modify it to include more mathematical mean/std dev/shape variables. – joshuakcockrell Dec 17 '19 at 01:26
  • 2
    Great answer, however I found a small bug in your code: ` if (num > 1 || num < 0) num = randn_bm(min, max, skew); // resample between 0 and 1 if out of range ` ...should be: ` if (num > 1 || num < 0) { num = randn_bm(min, max, skew); // resample between 0 and 1 if out of range } else { ... } return num; ` That is, only scale the final number if not re-sampling. – MushyMiddle Jan 15 '20 at 19:45
  • The function outputs strange distribution with a range of `[-90, 140]`: https://jsfiddle.net/vsync/frdzycv2/6 – vsync Mar 07 '21 at 10:26
  • 1
    Why can't I make it work when min is `0` and max is `1`? I want to mimic native `random` method which outputs only between 0 and 1 – vsync Mar 07 '21 at 13:05
  • @vsync Yeah something about this is broken at 0. Need to research more. – joshuakcockrell Mar 10 '21 at 03:59
  • @joshuakcockrell @vsync the issue is actually with the `round_to_precision` function in your jsfiddle. The way javascript's modulo handles negative numbers, causes your `round_to_precision` function to round _towards_ zero. So positive numbers round down, negative numbers round up. I fixed the `round_to_precision` function here: https://jsfiddle.net/jofxbdwr/1/ Also fun fact: javascript's `Math.round()` function also rounds towards zero. – woojoo666 Nov 13 '21 at 00:01
  • What's the significance of the number 3.6? – Isaac King Apr 24 '22 at 01:10
  • I have a question. i know random value is getting from seed(traditionally unixtime.). gaussain distribution isn't broken every different seed? – JaeIL Ryu May 03 '22 at 06:50
68

I want to know if the JavaScript function Math.random is normal distribution or not

Javascript Math.random is not a Normal Distribution(Gaussian bell curve). From ES 2015, 20.2.2.27 "Returns a Number value with positive sign, greater than or equal to 0 but less than 1, chosen randomly or pseudo randomly with approximately uniform distribution over that range, using an implementation-dependent algorithm or strategy. This function takes no arguments." So the provided collection when n is high enough we will get approximately uniform distribution. All values in the interval will have equal probability of appearance(straight line parallel to the x axis, denoting number between 0.0 and 1.0).

how can I get numbers which are normal distribution

There are several ways of getting collection of numbers with a normal distribution. As answered by Maxwell Collard the Box-Muller transform does transform uniform distribution to normal distribution(the code can be found in Maxwell Collard answer).

An answer to another stackoverflow answer to a question has a reply with other uniform distribution to normal distribution algorithms. Such as: Ziggurat, Ratio-of-uniforms, Inverting the CDF Besides one of the answers says that: says:

The Ziggurat algorithm is pretty efficient for this, although the Box-Muller transform is easier to implement from scratch (and not crazy slow).

And finally

I want to rebuilt a Schmidt-machine (German physicist), the machine produces random numbers of 0 or 1 and they have to be normal distributed so I can draw them in Gaussian bell curve.

When we have only two values (0 or 1) Gaussian curve looks the same as uniform distribution with 2 possible values. That is why a simple

function randomZero_One(){
    return Math.round(Math.random());
}

would suffice. It would return pseudo-randomly with approximately equal probability values 0 and 1.

Michael.Lumley
  • 2,345
  • 2
  • 31
  • 53
kodvin
  • 1,236
  • 9
  • 14
  • 24
    Just a heads up in case you are wondering where the upvotes are coming from. This answer was mentioned in a reddit thread, which triggered the meta effect . https://www.reddit.com/r/ProgrammerHumor/comments/a3ngy7/stackoverflow_is_a_weird_place/ – LukeG Dec 06 '18 at 20:08
  • 5
    ...And I posted that comment on Reddit. Now this answer has positive votes and I am proud of what I did. – SCLeo Dec 06 '18 at 23:41
  • Wow, that made my date, because this question was really old and i could not remove it. Shame stack gives max of 200 per day, so i would have got a 1000+ points .But still it feels amazing :D, thanks reddit for support(Also SCLeo) . :) – kodvin Dec 07 '18 at 06:58
  • 2
    All these answers, except a few ones, do not realize **the random distribution do not distribute between 0 and 1**, but can attain any arbitrarily high value in some cases. Thar is an outrageous confusion. – Brethlosze Apr 25 '21 at 06:34
59

I wanted to have approximately gaussian random numbers between 0 and 1, and after many tests I found this to be the best:

function gaussianRand() {
  var rand = 0;

  for (var i = 0; i < 6; i += 1) {
    rand += Math.random();
  }

  return rand / 6;
}

And as a bonus:

function gaussianRandom(start, end) {
  return Math.floor(start + gaussianRand() * (end - start + 1));
}
Dorian
  • 22,759
  • 8
  • 120
  • 116
  • 4
    This is actually a simple solution that works very well. Increasing the factor (6) there tightens up the distribution. – tadman Jan 12 '17 at 03:17
  • 11
    Worth it saying that this is an implementation of the (Central limit theorem)[https://en.wikipedia.org/wiki/Central_limit_theorem], being `6` the sample size. It privileges central numbers and forgot tail numbers comparing with Gauss curve. Here is an implementation of mine: http://plnkr.co/edit/jaky1FHCGpt81vs5Lohz?p=preview; – Andre Figueiredo Jan 18 '18 at 13:17
  • This is not a normal distribution it's a scaled normal distribution, as it's standard deviation is not 1 – GA1 Mar 30 '20 at 13:12
  • ```function gaussianRand() { var rand = 0; for (var i = 0; i < 6; i += 1) { rand += Math.random(); } return rand / 6; } And as a bonus: function gaussianRandom(start, end) { return Math.floor(start + gaussianRand() * (end - start + 1)); } ``` this works for start and end > 0 and < 1 – fedeb Feb 02 '21 at 20:19
19

The Javascript Math.random() pseudorandom function returns variates that are equally distributed between 0 and 1. To get a Gaussian distribution I use this:

// returns a gaussian random function with the given mean and stdev.
function gaussian(mean, stdev) {
  var y2;
  var use_last = false;
  return function() {
    var y1;
    if (use_last) {
      y1 = y2;
      use_last = false;
    } else {
      var x1, x2, w;
      do {
        x1 = 2.0 * Math.random() - 1.0;
        x2 = 2.0 * Math.random() - 1.0;
        w = x1 * x1 + x2 * x2;
      } while (w >= 1.0);
      w = Math.sqrt((-2.0 * Math.log(w)) / w);
      y1 = x1 * w;
      y2 = x2 * w;
      use_last = true;
    }

    var retval = mean + stdev * y1;
    if (retval > 0)
      return retval;
    return -retval;
  }
}

// make a standard gaussian variable.     
var standard = gaussian(100, 15);

// make a bunch of standard variates
for (i = 0; i < 1000; i++) {
  console.log( standard() )
}

I think I got this from Knuth.

Plot can be seen here

vsync
  • 118,978
  • 58
  • 307
  • 400
  • 8
    This is the [Marsaglia polar method.](https://en.wikipedia.org/wiki/Marsaglia_polar_method) – Rafi Mar 28 '16 at 06:56
  • 9
    Can you explain the reasoning behind the last 3 lines of the returned function? Why does `retval` have to be positive? What if the mean was, e.g., 0? Then I would expect both positive and negative samples. – Chris K Dec 16 '16 at 13:49
  • @ChrisK. Until reversed in sign, retvar will be the distributed around the negative of the supplied mean. If you pass 0 for the mean, it won't matter if you reverse the sign or not. In both cases, you will have an equal positive and negative distribution. – John Pankowicz Oct 22 '19 at 01:04
  • 2
    @ChrisK. You are right: due to the last 3 lines of code the value returned by `gaussian` function is always positive and so it cannot be a random value from a gaussian distribution. – Alessandro Jacopson Mar 01 '20 at 11:54
  • 2
    I think Knuth does not have `if(retval > 0) return retval; return -retval;` – Alessandro Jacopson Mar 01 '20 at 11:55
  • Fails for some combinations of values, legitimally able to being in a normal distribution. – Brethlosze Apr 25 '21 at 06:17
  • Be careful with the last part of the code that produce only positive numbers and therefore the results are no longer normally distributed. Commenting these two lines would solve the problem: // if (retval > 0) // return -retval; – Kardi Teknomo Dec 21 '22 at 12:09
14

Function that utilises the central limit theorem.

function normal(mu, sigma, nsamples){
    if(!nsamples) nsamples = 6
    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
}
Joe
  • 1,455
  • 2
  • 19
  • 36
  • My statistics knowledge is limited, but may I submit that @AndreFigueiredo's caveat may be very important. I found a more Gaussian normal distribution from translating the [Marsaglia polar method](https://en.wikipedia.org/wiki/Marsaglia_polar_method#Implementation) in place of this function. – Ross Rogers Mar 21 '18 at 16:54
  • 2
    Sure, the limit theorem means that the the distribution is better approximated in the limit of large `nsamples`. I would not uses this function for robust statistical analysis. If however you want a generator for the purposes of testing then the above works fine. The maximum distance from the center `mu` that is accessible is governed by `nsamples`. – Joe Mar 22 '18 at 10:34
  • I found that, to generate a better Gaussian, the equation should read: `return sigma*(run_total - nsamples/2)/Math.sqrt(nsamples/12) + mu` – ZephDavies Sep 18 '18 at 12:56
  • @ZephDavies, your code seems to have problems when nsamples != 6 – cowlinator Nov 01 '18 at 17:52
10

From the spec:

15.8.2.14 random ( )

Returns a Number value with positive sign, greater than or equal to 0 but less than 1, chosen randomly or pseudo randomly with approximately uniform distribution over that range, using an implementation-dependent algorithm or strategy. This function takes no arguments.

So, it's a uniform distribution, not normal or Gaussian. That's what you're going to find in just about any standard random number facility in any basic language runtime outside of specialized statistics libraries.

Andre Figueiredo
  • 12,930
  • 8
  • 48
  • 74
Pointy
  • 405,095
  • 59
  • 585
  • 614
9

You are confusing the output of the function (which is a uniform distribution between 0 and 1) with the need to generate a Gaussian distribution by repeatedly drawing random numbers that are either 0 or 1 - after a large number of trials, their sum will be approximately normally distributed.

You can use the Math.random() function, then round the result to an integer: if it's < 0.5, return 0; if its >= 0.5, return 1. Now you have equal probabilities of zero and one, and you can continue with the approach you described in your question.

Just to clarify: I don't think it's possible to have an algorithm that produces either 0's or 1's in a normally distributed way - normal distribution requires a continuous variable.

When you do the above for say 120 numbers, you will on average get 60 1's and 60 0's. The actual distribution you get will be the binomial distribution with a mean of 60 and a standard deviation of

stdev = sqrt(p(1-p)N) = 5.48

The probability of a particular number k when you have n samples with probability p (which we fixed at 0.5) is

p = n! / ((n-k)! k!) p^k (1-p)^(n-k)

When p = 0.5, you end up with just the binomial coefficients - which approach the normal distribution for n > 30, typically.

Floris
  • 45,857
  • 6
  • 70
  • 122
  • 2
    You're right about no such thing as a Gaussian distribution when it comes to binary (or unary). But one can be "digital"; eg, a bell curve could be constructed from (at the very least) trinary integer values (0, 1, 2), showing the effect of a Gaussian distribution (where 1 occurs the most, as the mean), though in order to get a proper curve shape that conforms to the 68-95-99.7 rule, the granularity would need to be higher. As an aside, your note about rounding an integer is an excellent way to get random boolean values! – Beejor Oct 10 '18 at 06:57
8

And a single line example:

Math.sqrt(-2 * Math.log(Math.random()))*Math.cos((2*Math.PI) * Math.random())

and a Fiddle https://jsfiddle.net/rszgjqf8/

unsalted
  • 355
  • 3
  • 14
  • Please explain and if possible, add a graph – vsync Mar 07 '21 at 12:34
  • @vsync It has been ~five years, but sure if I find time later tonight, I can break down the one liner, and maybe plot it if i'm feeling generous. – unsalted Mar 08 '21 at 18:40
  • 2
    This should be `Math.sqrt(-2*Math.log(1-Math.random()))*Math.cos(2*Math.PI*Math.random())` so you have never a zero value inside the `log`. – Brethlosze Apr 25 '21 at 06:28
5

For those interested in generating values of a normal distrubution, I would recommend checking this implementation of the Ziggurat algorithm in JavaScript: https://www.npmjs.com/package/node-ziggurat

The code of found in the author's page is:

function Ziggurat(){

var jsr = 123456789;

var wn = Array(128);
var fn = Array(128);
var kn = Array(128);

function RNOR(){
  var hz = SHR3();
  var iz = hz & 127;
  return (Math.abs(hz) < kn[iz]) ? hz * wn[iz] : nfix(hz, iz);
}

this.nextGaussian = function(){
  return RNOR();
}

function nfix(hz, iz){
  var r = 3.442619855899;
  var r1 = 1.0 / r;
  var x;
  var y;
  while(true){
    x = hz * wn[iz];
    if( iz == 0 ){
      x = (-Math.log(UNI()) * r1); 
      y = -Math.log(UNI());
      while( y + y < x * x){
        x = (-Math.log(UNI()) * r1); 
        y = -Math.log(UNI());
      }
      return ( hz > 0 ) ? r+x : -r-x;
    }

    if( fn[iz] + UNI() * (fn[iz-1] - fn[iz]) < Math.exp(-0.5 * x * x) ){
      return x;
    }
    hz = SHR3();
    iz = hz & 127;

    if( Math.abs(hz) < kn[iz]){
      return (hz * wn[iz]);
    }
  }
}

function SHR3(){
  var jz = jsr;
  var jzr = jsr;
  jzr ^= (jzr << 13);
  jzr ^= (jzr >>> 17);
  jzr ^= (jzr << 5);
  jsr = jzr;
  return (jz+jzr) | 0;
}

function UNI(){
  return 0.5 * (1 + SHR3() / -Math.pow(2,31));
}

function zigset(){
  // seed generator based on current time
  jsr ^= new Date().getTime();

  var m1 = 2147483648.0;
  var dn = 3.442619855899;
  var tn = dn;
  var vn = 9.91256303526217e-3;

  var q = vn / Math.exp(-0.5 * dn * dn);
  kn[0] = Math.floor((dn/q)*m1);
  kn[1] = 0;

  wn[0] = q / m1;
  wn[127] = dn / m1;

  fn[0] = 1.0;
  fn[127] = Math.exp(-0.5 * dn * dn);

  for(var i = 126; i >= 1; i--){
    dn = Math.sqrt(-2.0 * Math.log( vn / dn + Math.exp( -0.5 * dn * dn)));
    kn[i+1] = Math.floor((dn/tn)*m1);
    tn = dn;
    fn[i] = Math.exp(-0.5 * dn * dn);
    wn[i] = dn / m1;
  }
}
zigset();
}

Create a Ziggurat.js file and then:

var z = new Ziggurat();
z.nextGaussian();

For me it's working just perfect and as I had read in Wikipedia, this is a more efficient algorithm than the Box-Muller.

enter link description here

Eduardo G.R.
  • 377
  • 3
  • 18
3

I have tested several functions with the right configuration all work similarly and well.

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

Central limit is nice, must be with (n=3 for 6) and 12 for 12 to look as others. I configured others also to (6) or 12 or 1/12 as standard deviation, not sure why 12.

Central limit is a tiny bit less centered than Box/Muller and Ziggurat.

Box/Muller and Ziggurat look exactly the same

this variant by Joe(https://stackoverflow.com/a/33567961/466363) does standard deviation correctly:

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 is also nice but needs to be adjusted from z score to from 0 to 1 looks like it makes good numbers.

Box/Muller clipped is good but gives few repeated numbers at clipped edges but it is very similar to others, incorrect random numbers should be discarded not clipped.

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
}

Central limit variant it is called Bates distribution that is average https://en.wikipedia.org/wiki/Bates_distribution

not confused with Irwin Hall that is a sum https://en.wikipedia.org/wiki/Irwin%E2%80%93Hall_distribution

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

Shimon Doodkin
  • 4,310
  • 34
  • 37
3

skewnormal from normal and normal01

skewnormal(min, max, ..) returns a random number from the normal distribution that has been streched and offset to range from min to max, exponentially skewed with skew, and truncated to sigma standard deviations (in reverse order). Broken up into logical steps normal and normal01 for clarity and to generate random numbers directly from these intermediate functions if desired. (Plus a bonus 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://stackoverflow.com/a/74258559/213246
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());

Based on another popular answer by joshuakcockrell. You may prefer this implementation because: 1. it's factored to portray intermediate functions, 2. it exposes mathematically relevant and useful sigma parameter, 3. it has better names and comments.

See the JSFiddle for the complete demo environment, which makes it easy to define then test and visualize your own random distribution functions as pictured below:

visualization of above distribution functions

View interactive charts: https://jsfiddle.net/rgefzusq/34/show/ Playground: https://jsfiddle.net/rgefzusq/34/

infogulch
  • 1,182
  • 10
  • 22
2

A non verbose function to sample a random value from a Gaussian distribution I wrote some time ago:

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;
}

It should work if you clamp the values to the range you want.

  • 4
    this function looks like boobs http://jsfiddle.net/p3y40gf3/16/ (its not gauss shape), anyways it is interesting – Shimon Doodkin May 16 '18 at 07:33
  • 4
    @ShimonDoodkin whoops, nice spot, you're right, I'd copied a modified version of the function. I've updated to the correct one now and all looks good: http://jsfiddle.net/4334Lnh9/ – SuperEggbert May 20 '18 at 08:40
  • 1
    just a note: clamping values is an incorrect idea, better to re-sample again. otherwise, you would get fat tails. – Shimon Doodkin Nov 22 '20 at 05:29
1

This is my JavaScript implementation of Algorithm P (Polar method for normal deviates) from Section 3.4.1 of Donald Knuth's book The Art of Computer Programming:

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))
    }
} 

Use it like that:

var standard_normal = gaussian(0,1)
var a_standard_normal_deviate = standard_normal()
Alessandro Jacopson
  • 18,047
  • 15
  • 98
  • 153
0

I found this library that includes lots of useful Random functions. You can either install it via simjs from npm, or just take the random-node-*.js file out directly for what you need.

http://www.simjs.com/random.html http://www.simjs.com/download.html

Mark
  • 538
  • 6
  • 17
0

This is my solution to the problem, using the Marsaglia polar method. The range depends on the parameters you give, without parameters it almost never generates anything outside of the range.

As it generates two normally distributed numbers per iteration, I declared a variable under window.temp.spareNormal to grab the spare one if it's there. Might not be the best location for it, but hey.

You'd probably have to round the result in order to get what you want.

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;
}
Sjor
  • 9
  • 2
  • I'd simplify your implementation and lose the dodgy global, so: `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 Jul 05 '17 at 11:29
  • Comments lose newline characters :( – alecmce Jul 05 '17 at 11:29
0

for finding normal distribution of value:

getNormal = (x, mean, standardDeviation, ) => {
 return (1 / standardDeviation * Math.sqrt(2 * (3, 14))) * Math.pow(Math.E, -Math.pow(x - mean, 2) / (2 * (standardDeviation * standardDeviation)));  
}
Kirill Matrosov
  • 5,564
  • 4
  • 28
  • 39
0

The only sort of qualifications I have for this is having taken a single statistics class. If I get something wrong, please tell me, I'd like to learn more about statistics and I don't want to keep thinking something wrong.

If you want to create a random number generator that produces numbers in a normal distribution, you should be able to take samples from a uniform distribution, which is no problem. If you set up a basic random number generator that generates numbers in range a to b, the distribution of values produced will have µ = (a+b)/2 and σ = (b-a)/√12. If the mean of a a few sample of values (≥30) taken from this distribution is taken for many such samples, then for the sampling distribution µ (sample means) = µ (population mean) and σ (sample means' stdev) = σ (population stdev)/√n (number of values in the sample).

By controlling the mean and stdev of the original distribution, you can control the ending mean and standard deviation of a random number generator that produces a normal distribution.

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);
}
MartenCatcher
  • 2,713
  • 8
  • 26
  • 39
0

Just in case: Math.pow(Math.random(), p).

For example:

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 test:

graph

Artfaith
  • 1,183
  • 4
  • 19
  • 29
-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;
 }

}
-3
//This is what I use for a Normal-ish distribution random function.
function normal_random(){
  var pos = [ Math.random(), Math.random() ];
  while ( Math.sin( pos[0] * Math.PI ) > pos[1] ){
    pos = [ Math.random(), Math.random() ];
  }   
  return pos[0];
};

This function returns a value between 0 and 1. Values near 0.5 are returned most often.

bas080
  • 341
  • 3
  • 9
  • 1
    I'm by no means a math genius, but my implementation of your algorithm ([JSFiddle](http://jsfiddle.net/dje3z093/1/)) returns the exact opposite. 0.5 is being returned least often. To avoid confusion I think you should also explicitly mention that this is not the logarithm for calculating a random point in a normal distribution, but a random point on the circumference of a circle. – Pascalculator Jan 16 '15 at 01:15
  • A Gaussian distribution is centered in zero and is unbounded. Does this sound anything like your algorithm? – Ricardo Magalhães Cruz Feb 13 '16 at 10:02
  • 1
    need to change the sign from > pos[1] to < pos[1], otherwise looked like a bowl http://jsfiddle.net/p3y40gf3/17/ – Shimon Doodkin May 16 '18 at 07:38