30

Say, if I wanted to generate an unbiased random number between min and max, I'd do:

var rand = function(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
};

But what if I want to generate a random number between min and max but more biased towards a value N between min and max to a degree D? It's best to illustrate it with a probability curve:

enter image description here

6

4 Answers 4

47

Here is one way:

  • Get a random number in the min-max range
  • Get a random normalized mix value
  • Mix random with bias based on random mix

Ie., in pseudo:

Variables:
  min = 0
  max = 100
  bias = 67      (N)
  influence = 1  (D) [0.0, 1.0]

Formula:
  rnd = random() x (max - min) + min
  mix = random() x influence
  value = rnd x (1 - mix) + bias x mix

The mix factor can be reduced with a secondary factor to set how much it should influence (ie. mix * factor where factor is [0, 1]).

Demo

This will plot a biased random range. The upper band has 1 as influence, the bottom 0.75 influence. Bias is here set to be at 2/3 position in the range. The bottom band is without (deliberate) bias for comparison.

var ctx = document.querySelector("canvas").getContext("2d");
ctx.fillStyle = "red"; ctx.fillRect(399,0,2,110);  // draw bias target
ctx.fillStyle = "rgba(0,0,0,0.07)";

function getRndBias(min, max, bias, influence) {
    var rnd = Math.random() * (max - min) + min,   // random in range
        mix = Math.random() * influence;           // random mixer
    return rnd * (1 - mix) + bias * mix;           // mix full range and bias
}

// plot biased result
(function loop() {
  for(var i = 0; i < 5; i++) {  // just sub-frames (speedier plot)
    ctx.fillRect( getRndBias(0, 600, 400, 1.00),  4, 2, 50);
    ctx.fillRect( getRndBias(0, 600, 400, 0.75), 55, 2, 50);
    ctx.fillRect( Math.random() * 600          ,115, 2, 35);
  }
  requestAnimationFrame(loop);
})();
<canvas width=600></canvas>

6
  • 4
    Very nice. Thanks for the demo.
    – c00000fd
    Mar 30, 2015 at 2:29
  • 2
    @c00000fd no problem! I forgot to mention that the bias can be more influential by going above 1 and clamp down the result in the mix. But you probably already see this. Good luck with your project!
    – user1693593
    Mar 30, 2015 at 3:09
  • What happens if I want to have value of 1 or 0, but i want the outcome to be 67% 1s? Jun 8, 2018 at 14:36
  • 2
    Generate a random number between 1 and 100. If it's 67 or more, pick 0, otherwise, pick 1 Jul 25, 2020 at 0:38
  • My god this is amazing. Well done! Apr 1, 2023 at 5:41
8

Fun: use the image as the density function. Sample random pixels until you get a black one, then take the x co-ordinate.

enter image description here

Code:

getPixels = require("get-pixels"); // npm install get-pixels

getPixels("distribution.png", function(err, pixels) {
  var height, r, s, width, x, y;
  if (err) {
    return;
  }
  width = pixels.shape[0];
  height = pixels.shape[1];
  while (pixels.get(x, y, 0) !== 0) {
    r = Math.random();
    s = Math.random();
    x = Math.floor(r * width);
    y = Math.floor(s * height);
  }
  return console.log(r);
});

Example output:

0.7892316638026386
0.8595335511490703
0.5459279934875667
0.9044852438382804
0.35129814594984055
0.5352215224411339
0.8271261665504426
0.4871773284394294
0.8202084102667868
0.39301465335302055

Scale to taste.

3
  • 5
    This is dumb and brilliant and I literally laughed out loud. It's also the most accurate one on this page! Jul 25, 2020 at 0:46
  • ikr @MooingDuck
    – user13944038
    May 22, 2021 at 1:52
  • The fine and ever so blurry line between brilliance and madness, ladies and gentlemen.
    – A-Tech
    Jan 4, 2022 at 15:33
5

Just for fun, here's a version that relies on the Gaussian function, as mentioned in SpiderPig's comment to your question. The Gaussian function is applied to a random number between 1 and 100, where the height of the bell indicates how close the final value will be to N. I interpreted the degree D to mean how likely the final value is to be close to N, and so D corresponds to the width of the bell - the smaller D is, the less likely is the bias. Clearly, the example could be further calibrated.

(I copied Ken Fyrstenberg's canvas method to demonstrate the function.)

function randBias(min, max, N, D) {
  var a = 1,
      b = 50,
      c = D;

  var influence = Math.floor(Math.random() * (101)),
    x = Math.floor(Math.random() * (max - min + 1)) + min;

  return x > N 
         ? x + Math.floor(gauss(influence) * (N - x)) 
         : x - Math.floor(gauss(influence) * (x - N));

  function gauss(x) {
    return a * Math.exp(-(x - b) * (x - b) / (2 * c * c));
  }
}

var ctx = document.querySelector("canvas").getContext("2d");
ctx.fillStyle = "red";
ctx.fillRect(399, 0, 2, 110);
ctx.fillStyle = "rgba(0,0,0,0.07)";

(function loop() {
  for (var i = 0; i < 5; i++) {
    ctx.fillRect(randBias(0, 600, 400, 50), 4, 2, 50);
    ctx.fillRect(randBias(0, 600, 400, 10), 55, 2, 50);
    ctx.fillRect(Math.random() * 600, 115, 2, 35);
  }
  requestAnimationFrame(loop);
})();
<canvas width=600></canvas>

2
  • Thanks. It would really help if you added a live sample like Ken Fyrstenberg did in his post. It really helps to see the effectiveness or bias of the PRNG.
    – c00000fd
    Mar 30, 2015 at 2:31
  • @c00000fd added! Thinking about the shape and location of the "bell", as well as other parameters, could help refine the results. Mar 31, 2015 at 4:44
4

Say when you use Math.floor(Math.random() * (max - min + 1)) + min;, you are actually creating a Uniform distribution. To get the data distribution in your chart, what you need is a distribution with non-zero skewness.

There are different techniques to get those kinds of distributions. Here is an example of beta distribution found on stackoverflow.


Here is the example summarized from the link:

unif = Math.random()  // The original uniform distribution.

And we can transfer it into beta distribution by doing

beta = sin(unif*pi/2)^2 // The standard beta distribution

To get the skewness shown in your chart,

beta_right = (beta > 0.5) ? 2*beta-1 : 2*(1-beta)-1;

You can change the value 1 to any else to have it skew to other value.

1
  • 2
    Thanks for the explanation. While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes.
    – Lea Cohen
    Mar 29, 2015 at 11:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.