4
votes

My task:

Generate random numbers between 1 and 20, to 1 decimal place.

However my issue as simple as mt_rand. I want most of the numbers generated to be lower around 0.5 - 4.5 with the occasional number being between 4.5-10 and very rarely say once every 12-20 hours being between 10-20.

I've been using the following but have no idea where to go from. I am a very basic self-taught programmer.

$min = 1;
$max = 20;
$suisse_interest = mt_rand ($min*10, $max*10) / 10

Maybe if I briefly explain why I want this it may help..

I own an online game and want to add 3 "banks" with each bank generating different interests each hour. Most of the time I want it low, but sometimes higher and very rarely very high (15-20%).

With the above code the random number goes too high to often.

Any help with this is greatly appreciated!

3
Since the distribution you want to create is monotone decreasing, you can use the ziggurat algorithm to map your distribution (uniform) onto the one you want (which you can arbitrarily create). Or you could use the box muller transformation, but it's less efficient (and since you're doing this a lot, you might want to precompute the tables described in the ziggurat algorithm). - will

3 Answers

3
votes

You need an exponential calculation. If you use a function similar to the following function, the probability for low numbers increases. Of course you need to adapt the numbers a bit to provide an output suiting your needs.

$i = 0;
while($i<30) {
    $i++;
    $rand = mt_rand(0, 7000) / 100; // 0.0 ~ 70.0
    // This is the most important line:
    $output = round( 20*(pow(0.95,$rand)) , 1);
    echo "$output ";
}

Sample output:

1.8  4.3  2.6  5.5  3.7  15.5  1.6  0.6  0.6  1.6  5.8  
1.3  6.1  3.2  0.8  1.7  14.7  7.9  1.3  10.3  5.5  12.6  
1.5  8.4  1.5  0.9  13.3  5.8  7.5  1.7  

As you see, mostly smaller number are printed.

Chart

The probability to get 20 is around 1.4% in my code whereas the probability to get a number smaller than 5 is around 78%

2
votes

Try this.The probability to 1.0~4.5 is around 96%, 4.5~10.0 is around 2%, and 10.0~20.0 is around 2%.

<?php
    // 1.0~4.5    96%
    // 4.5~10.0   2%
    // 10.0~20.0  2%

    function fun() {
        $num = mt_rand(1, 100);
        if ($num > 0 && $num <= 96) {
            $return = mt_rand(10, 45) / 10;  // 96%
        } else if ($num > 96 && $num <= 98) {
            $return = mt_rand(45, 100) / 10;  // 2%
        } else {
            $return = mt_rand(100, 200) / 10;  // 2%
        }
        return sprintf("%01.1f",$return);
    }

    echo fun();
?>
0
votes

This is not a PHP-specific problem.

What you need is a non-linear probability law, that you can then implement in PHP.

If you want something centered around an average value, the ideal would be a gaussian aka normal distribution, but computing it requires various complicated tricks, most of them being optimized for rapid generation at the cost of increasing complexity.

If you generate only a few values each hour, performance will not be an issue.

A reasonable approximation would be to sum 3 or 4 random variables, taking advantage of the central limit theorem.

Summing random values between 0 and twice your middle rate will create an approximation of a gaussian centered around your middle value. You can then clamp values inferior to the middle point if you don't want low rates. The net result would be 50% chances of getting middle rate and a steadily decreasing chance to get up to twice that value.

An increasing number of sums will "narrow" the curve, making it less likely to get a high value.

for instance:

define ("INTEREST_MEAN", 10);
define ("INTEREST_SPREAD", 5);
function random_interest ()
{
    $res = 0;
    for ($i = 0 ; $i != INTEREST_SPREAD ; $i++) $res += mt_rand(0, 2*INTEREST_MEAN);
    $res /= INTEREST_SPREAD; // normalize the sum to get a mean-centered result
    $res = max ($res, INTEREST_MEAN); // clamp lower values
}