Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

This question already has an answer here:

I have an input of two values, let's say 1 and 10. Lesser numbers should occur more often in the output than higher numbers.

1: extremely common  |  most likely
2: very common       |
3: pretty common     |
4: common            |  decreasing in likeliness
5: somewhat common   |
...                  |
10: extremely rare   V  least likely

How do I achieve this? My code until now looks like this:

function getValue($min, $max) {
    return random($min, $max);
}
share|improve this question

marked as duplicate by Alexandre Vaillancourt, Kromster, bummzack, Anko, msell Feb 20 at 22:36

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

3  
Really interesting question. Check out this post: stackoverflow.com/questions/4420502/… – Jon Feb 18 at 12:47
2  
...and this post. – Alexandre Vaillancourt Feb 18 at 12:53
2  
Do you just want to get whole numbers? Or any number in the range e.g. 0.5, 7.9834... – Malrig Feb 18 at 13:22
6  
Define "very common" vs. "extremely common". If you generate a million numbers how many of each do you want in the result (roughly). – ratchet freak Feb 18 at 13:51
1  
I give a gentle introduction to the mathematics of generating an arbitrary probability distribution when given a uniform probability distribution here: ericlippert.com/2012/02/21/generating-random-non-uniform-data -- the code is in C# but you can adapt the mathematics to the language of your choice. – Eric Lippert Feb 18 at 17:40

12 Answers 12

up vote 49 down vote accepted

Assuming you have a random() function that returns a uniformly-distributed numeric value in the interval [0, 1)...

(I see the edit attempt to "fix" the mismatched bracket above, but this is deliberate and carries specific meaning)

random() - random()

Gives a distribution that peaks at 0 and falls off toward -1 and 1.

abs(random() - random())

Peaks at 0 and falls off toward 1

 floor(abs(random() - random()) * (1 + max - min) + min)

Gives you a random number between min and max, with outputs closer to min being more common, falling off linearly toward the max.

Using inverse transform sampling you can get this same linear distribution with one random sample via the formula:

1 - sqrt(1 - random())

(and we can apply the same scale/offset/floor approach to get a corresponding discrete distribution)

You can find methods to get custom-shaped probability distributions in this answer or the links suggested in comments above.

share|improve this answer
6  
Ooh! That's clever! – Alexandre Vaillancourt Feb 18 at 13:45
2  
@user1306322 because random() is uniformly distributed on [0, 1), so random() * 2 - 1 is uniformly distributed on [-1, 1) - it doesn't have the non-uniformity OP has asked for. By taking two independent random rolls a and b, we introduce some non-uniformity: there are more ways to get 0 (all cases a == b) than there are to get 1 (only a == 1, b == 0 fits the bill - this is a simplification since 1 is actually outside the range of this function, but the extreme is easier to talk about) – DMGregory Feb 18 at 15:47
    
To expand on this answer: this trick works with random() returning float/double but it doesn't work if you use random integers, because the probability of returning min will be about half of what it should be. For continuous or high-resolution outputs, the probability of returning min is approximately 0 anyway so it doesn't matter. – Dietrich Epp Feb 18 at 19:10
    
@DietrichEpp I've updated the rounding function to floor, which should keep the distribution linear, including for the min & max buckets. – DMGregory Feb 18 at 19:38
2  
Not that I'm complaining about the upvotes, but don't forget to spread the love a bit. ;) This was just a quick off-the-cuff answer - the power-based approaches in some of the other answers offer more flexibility in the falloff of the probability distribution, with similar simplicity. – DMGregory Feb 19 at 15:51

Let's say "rand()" gives you a random number between 0 and 1 (inclusive).

pow(rand(), 2)

will give you an answer between 0 and 1 (inclusive), but the result is more likely to be close to zero, following a quadratic curve.

pow(rand(), 0.5)

will give you an answer between 0 and 1 (inclusive), but the result is more likely to be close to one, following a square root curve.

And since the min and max of the output are still 0 and 1, this distribution can be mapped to any range (given min, max, and the exponent p) like so:

result = min + (max - min) * pow(rand(), p)

Using different values of p can bias you towards the minimum or maximum value depending on your needs: graph of x raised to different powers (example of mapping a 0-1 range by raising x to different powers. From top to bottom: 1/4, 1/2, 2, 4)

Experiment with different powers to find what works for you. Just remember that bigger powers greater than one will bias you towards the minimum value, and smaller powers less than one but greater than zero will bias you towards the maximum value.

You can also swap max and min to flip the distribution of results, and if you want integer results, I'd use floor(x), increase max by one, and either use a rand() function that is exclusive at the top end (won't return 1.0) or clamp the final output to the range to filter out that rare case.

share|improve this answer
2  
This is the answer I was going to offer, only better illustrated +1! – Wren Feb 19 at 16:51

Why not use a system similar to advantage/disadvantage as used in DnD 5e?

It boils down to: disadvantage: roll 2 (or any number of) dice and keep the lowest. advantage: roll 2 (or any number of) dice and keep the highest.

for 1 out of 2 dice this gives a linear chance decreasing as you get higher:

example: http://anydice.com/program/1227

for one out of more dice this gives a curve:

example: http://anydice.com/program/585d

And picking more dice out of the set favors numbers in the lower-middle of your range.

I hope this helps

share|improve this answer
    
Was going to post this, but you beat me. :) Also, you might check out this rpg.SE post that uses a very similar method. – MichaelS Feb 19 at 0:40

A naive and not efficient algorithm that would give you a linear distribution:

vector values;
int iterations = 1;
for ( val = max; val >= min; --val ) {
  for ( i = 1; i <= iterations ; ++i )
    values.push_back( val );

  ++iterations;
}

// The goal is to have in your values vector something like this for min = 1 and max = 4:
// [1, 1, 1, 1, 2, 2, 2, 3, 3, 4]

return values[random(0, values.size() - 1)];

This will give you what you need.

share|improve this answer
1  
nice idea, but really inefficient for bigger number ranges. :( – wernersbacher Feb 18 at 12:42
    
@wernersbacher you're absolutely right ;) That's why I said it in the first sentence! I'm sure someone else will come up with a better solution. – Alexandre Vaillancourt Feb 18 at 12:43
    
We can bring it to O(n) I think. First loop, i=0; for(n=min to max){i+=n; values[n]=i} (If I did it right, you have an array with {1, (1+2), (1+2+3), etc.}.) Switch to for(n=max to min) so it's what the OP wants {max, (max+[max-1]), etc.}. Next, roll r=random(1, i) (inclusive, and i should equal the last value in the values array). Then, for(n=1 to values.length){if(i<=values[n]){return n}}. This way rolling 1 to a million just does 2 million iterations, instead of a million million. – MichaelS Feb 19 at 0:55
1  
The generation of the list may be inefficient, but if its done once and stored as a static, its not so bad. The line return values[random(0, values.size() - 1)]; isn't inefficient. – Octopus Feb 19 at 17:38

You can use a local variable to save a random call:

function F(min, max) {
  var t = random(); //Assuming this returns a value between 0 and 1
  return min + (max - min) * t * t;
}

This makes a non linear distribution where values close to min are more frequent. You can force even more extremal distribution by inserting t = t*t or user a pow with other values.

share|improve this answer
    
"to save a random call"? Save from what? If the algorithm needs two calls, it needs to calls, and if it needs one call, it needs one call. I'm not sure why you'd "save" a call to random. – Alexandre Vaillancourt Feb 18 at 14:49
1  
@AlexandreVaillancourt To save one call to random() as compared to the formula in my answer which takes the difference of two calls. – DMGregory Feb 18 at 14:56
2  
Or just do return min + (max - min) * pow(random(), 2). Still +1 for smart use of Uniform Product Distribution. – Dorus Feb 18 at 15:16
    
@AlexandreVaillancourt Some PRNGs are computationally intensive. But then pow() may be as well, depending on floating-point performance of a particular CPU and optimizing power of the compiler. Benchmark. – Damian Yerrick Feb 19 at 23:07

If this is for a game, often you want to tweak values intuitively rather than rely on some existing distribution.

A good way to do this is to pick numbers from an array, and duplicate the values you want more than one. For example, here's some python code that does that. The nice thing about it is that you can see pretty easily the probability of the different options.

import random
values = [
1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3,
4, 4, 4, 4,
5, 5, 5, 5,
6, 6, 6,
7, 7,
8,
9,
10
]
random.choice(values)

Obviously this only makes sense with a small number of values.

Alternatively, if you have a probability distribution in mind you can use Inverse Transform Sampling. It can take the uniform random number from 0 to 1 and map it so that it matches any probability distribution. It's requires some maths, but if you have some good libraries they should be able to help you out.

share|improve this answer
    
This is effectively Alexandre Vaillancourt's answer, but without the method to generate the desired table without specifying every entry explicitly. – DMGregory Feb 19 at 0:16
3  
@DMGregory: Two differences. First, this one is more time efficient (the data already exists at runtime, and doesn't need to be created). Second, you can tweak the curve precisely as you see fit, instead of trying to come up with some formula or logic that approximates the curve you're going for. – MichaelS Feb 19 at 0:46
    
The curve here only matches what you want only if the probabilities of each numbers are a small integer ratio with each other. There are many cases where you may want a non-integer distribution probabilities. In this particular implementation, for example, 8,9,10 have the exact same probability of occurring. – Lie Ryan Feb 20 at 11:39

If you have uniformly distributed random numbers, you can use those as the parameter to a mathematical function that has a exponential-like shape, so that most numbers in the random range yield a low value. The initial random input can be normalized, then scaled to your range, so the function is well-behaved.

For example, the following function, if given normalized values from 0 to 1 as input:

f(x)= 10 * 2^((x-1.4)*10)+0.1

You can tweak the parameters to make this cover the whole output range from 0 to 1. And if you need higher numbers, just multiply or offset the result.

I didn't check this mathematically, but it seems good enough experimentally.

share|improve this answer
1  
I like this answer because it made me understand what the functions of the other answers actually do mathematically. – problemofficer Feb 19 at 22:36

Couple other ideas:

Folded Normal Distribution.

enter image description here

This is achieved by generating a standard Gaussian random, then running it through abs(). Values greater than 1 are possible, feel free to deal with it however you choose; clamp it, re-randomize, ignore.

Wald Distribution.

Also known as the Inverse Gaussian.

enter image description here

0 as a result has a very low probability, while values near 0 have a very high probability, trailing off towards infinity (see above about clamping).

share|improve this answer
    
Really cool concepts – theonlygusti Feb 20 at 15:58
    
@theonlygusti Yup, I actually implemented the Wald distribution in the Custom Ore Gen mod for Minecraft so I could use it in my ore distributions. Long, generally straight veins with rare, but hard, corners. – Draco18s Feb 20 at 17:35
    
That's pretty cool, but surely the fact that y=0 when x=0 would have complicated its implementation into your mod? Doesn't the slope down towards the origin mess anything up? – theonlygusti Feb 20 at 22:12
    
@theonlygusti The random is a relative modifier, not an absolute one. Also, remember that Y here is a probability value, not a return value. X is the return value from the rand_wald() function. – Draco18s Feb 21 at 0:47

The important thing is to re-map the function from 0-1 to what you desire. A big part of this depends on just what distribution you want from it. I suspect you want something that has an exponential decaying type curve in it, although you have to really ask yourself what you want the distribution to look like. A couple of possibilities are an exponential decay function, and a inverse function, although in both cases the edge cases need to be well studied. I'm not going to give you the code, but rather the formulas. Assume R is a random number between 0-1.

Inverse Linear:

while (R<.1) {
    R*=10;
}
return ceil(10-(1/(R))

Exponential Decay:

// Will likely need to work on some edge cases
return 10-10*(e-e^R)

To test it, I suggest that instead of using real random numbers, iterate through, say, every 0.01 value of R, and see if the distribution is correct. If there are points that lie outside your range, find a way to remove them. Good luck!

share|improve this answer

I recommend using the function:

f(s,g) = round(s+R^n(g-s))

where s and g represent your minimum and maximum values, respectively, R is a random number between 0 and 1 (inclusive) and n is a pre-determined constant. The notation ||x|| represents the nearest integer to x.

An example in PHP 5:

function biasedRandom($min, $max) {
  $power = 2;
  # lcg_value() returns a random number between 0 and 1
  $bias  = pow(lcg_value(), $power);
  return round($min + ($max-$min) * $bias);
}

In an IPython notebook I ran my function 1000 times each for different powers. It generated this graph, where each value (x-axis) is plotted against the probability of it occurring (y-axis):

p=2 p=4 p=8 Higher powers have steeper graphs

The graph shows that as p, the power which we raise the random number to, increases, the 'steepness' of the graph also increases.

That is to say, a higher value of p will make the smallest value, in this case 1, to have a much higher probability of occurring than the highest value, 10. See the green line, where p equals 8.

In the case of the blue line, where p is only equal to 2, the bias is far less extreme, and all numbers are closer in probability.

This animation demonstrates how the probabilities of each value change with different values of p:

https://www.desmos.com/calculator/9pi4bwnc7y

I used this python function to generate each of the biased random numbers used as data in all of the following graphs.

def biasedRandom(smallest, largest, p): return int(round(smallest + (largest - smallest) * pow(random,p)))

These box-plots each show the distribution of 500 values generated by my function, with powers of (from top to bottom) 2, then 2.5 then 4.

box-plots

The box-plots clearly show that smaller numbers are more common than larger numbers (the medians are all less than 5.5), and that there is a higher density of values towards the lower end of the spectrum. However, one can also see how high values (all the way up to ten) still emerge sometimes.

If we compare the box-plots we can again see that a lower power (the top-most box-plot has the lowest power, in this case 2) leads to a more even spread of values, with high values appearing more frequently and low values appearing less frequently than when using higher powers.

Without rounding the function's final result, we can obtain the following visualisation, which shows clearly the higher density of smaller numbers. Values are plotted as they are obtained by the function.

https://jsfiddle.net/theonlygusti/zexhrL2p/1/
The above image shows the distribution of results for powers 8, 16 and 32. The layout is intended to represent a number line, with 1 on the far left and 10 on the right.

share|improve this answer

One can use a simple formula like so:

function getValue(min,max) {
    return min-1+Math.pow((max-min+1),Math.random());
}

When given min as 1 and max as 10 this generates a number from 1.0 to 10.0 where 10.0 is rare. Remember Math.random() generates a uniform random number from 0.0 to 1.0

Here is some R code to show you the results:

(function(min,max){ 
    x=min-1+(max-min+1)^runif(10000); 
    print(summary(x)); 
    hist(x)
 })(4,28)

histogram

share|improve this answer
    
Interesting concept, but doesnt this achieve the opposite effect? 1111 1111(max) XOR 0000 0001(rand) would give 1111 1110 right? the most significant bit has a 50% chance of staying up thus there would be a tendency of producing high numbers, also max doesn't limit the outcome of rand. I like the concept though. Would & achieve the desired effect? – Niels Feb 19 at 11:58
    
I meant hat to be the power operator. I've been doing a lot of R programming recently. I changed it to POW(x,y) – Chris Feb 19 at 14:18

You could call the random function twice:

 function getValue($min, $max) {
     return random($min,random($min, $max));
 }

For numbers 1 - 5, the probabilities for getValue(1,5) are
1 = 137 out of 300
2 = 72 out of 300
3 = 47 out of 300
4 = 27 out of 300
5 = 12 out of 300

share|improve this answer
    
This is effectively a subset of user2645227's answer. – DMGregory Feb 19 at 0:18

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