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!
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.
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%
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();
?>
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
}
Related
I would like to calculate the spending factor in a rule where we spend Nth time the previous payment done
Here is an example of spending.
firstPaymentAmount=10
SpendingFactor=5
PaymentCount=4
payment1: 10
payment2: 50 (= 10 x 5)
payment3: 250 (= 50 x 5)
payment4: 1250 (= 250 x 5)
At the end we get the sum of all payment made and we have :
10 + 50 + 250 + 1250 = 1560
I would like to know the formula that let me retrieve the spending factor (=5 here) by only knowing these parameters:
paymentCount = 4
initalPayment = 10
totalPaid = 1560
By knowing the formula to calculate the spendingFactor, I will then be able to know the amount and detail of each payment.
I've came with a solution. It gets an approximation of the spending factor because there is no direct formula.
Getting the exact spending factor is too CPU intensive. So, I'm calculating a near approximation first. This value will always be above the solution. I then decrease that number until I'm getting below the total paid amount.
Here is a PHP sample of what I've done.
$paymentCount = 4;
$initialPayment = 10;
$totalPaid = 1560;
//----- precalculate the factor based on total payment for faster computation
//----- this predefined factor will always be above our final factor
$estimatedSpendingFactor = exp(log($totalPaid) / $paymentCount);
//----- find the estimated spending factor
do
{
$estimatedSpendingFactor -= 0.0001;
$y = $initialPayment * (pow($estimatedSpendingFactor, $paymentCount) - 1)
/ ($estimatedSpendingFactor-1);
}
while ($y > $totalPaid);
//-----
printf("The spending factor is %f\n", $estimatedSpendingFactor);
the output will be :
The spending factor is : 5.000000
If I wanted a random number between one and three I could do $n = mt_rand(1,3).
There is a 33% chance that $n = 1, a 33% chance it's 2, and a 33% chance that it's 3.
What if I want to make it more difficult to get a 3 than a 1?
Say I want a 50% chance that a 1 is drawn, a 30% chance that a 2 is drawn and a 20% chance that a 3 is drawn?
I need a scalable solution as the possible range will vary between 1-3 and 1-100, but in general I'd like the lower numbers to be drawn more often than the higher ones.
How can I accomplish this?
There is a simple explanation of how you can use standard uniform random variable to produce random variable with a distribution similar to the one you want:
https://math.stackexchange.com/a/241543
This is maths.
In your example the just chose a random number between 0 and 99.
Values returned between 0 to 49 - call it 1
Values returned between 50 - 69 - Call it 2
Values returned between 70 - 99 - Call it 3
Simple if statement will do this or populate an array for the distribution required
Assuming a 1 - 10 scale, you can use a simple if statement and have the numbers represent percentages. And just have each if statement set $n to a specific. Only downfall, it isn't universal.
$dummy = mt_rand(1,10);
// represents 50%
if ($dummy <= 5) {
$n = 1;
}
// represents 40%
if ($dummy >= 6 && $dummy <= 9) {
$n = 2;
} else {
// represents 10%
$n = 3;
}
I'm not sure if this title is correct but here's basically what I am trying to do.
I am trying to check if a number is less than 100 and if it isn't I would like to know what factor of 10 I need to divide it by to get below 100 i.e. for 7923 the factor is 100 to make it 79.23 and for 452,936,489 the factor would be 10,000,000 to make it 45.2936489.
Is there a function or a piece of script that does that out there?
Cheers
$number = 452936489;
$factor = pow(10, ceil(log($number/100) / log(10)));
Ok. basic math:
you need to find a power of 10 divisor that reduces your number below 100, so the log business figures out the exact fractional power of 10 required to turn 10 into your original number. That comes out to be around 6.6560373....
That gets rounded up to 7, and is then used to raise 10 to that power.
10^7 = 10,000,000
452936489 / 10^7 = 45.2936489
<?
$num = 7923;
$x = 10;
while(true)
{
$result = $num/$x;
if($result < 100)
{
die($x."");
}
else
{
$x *= 10;
}
}
?>
How would I go about creating a random number generator which has a bias to be within a range of numbers?
say I have this:
$rnum = rand(0,200);
so $rnum == 3 and next time $rnum == 106 then $rnum == 10 and so on...
But I would rather have a bias range of say 80 - 120 so it would more likely select a number within the bias range than outside of it.
$rnum == 86 and next time $rnum == 112 then $rnum == 93 but still be able to $rnum == 24 and so on...
I thought I might have been able to do this:
if($rnum < $middle_of_bias){
$rnum++;
}else{
$rnum--;
}
but didn't really work, as you can see if $rnum == 1 after applying the bias it would only make it 2 not making this method very successful.
Thanks for your help.
Edit: everyones answers were great. I really liked gnur's one and Phil H's I have made my modifications to rmflow's one and it is working how I wanted it to. Thanks to every one that helped!
if (rand(0, 10) > 2)
{
$rnum = rand($low, $high);
}else{
$rnum = rand(0, $max);
}
if (rand(0, 10) > 7)
{
$rnum = rand(80, 120);
}
else
{
$rnum = rand(0, 200);
}
You probably want to use rand() to get a random number from a larger range in a uniform distribution and then use a function to map that larger range to the smaller range you actually want in a way that produces the distribution you want.
Another possibility to bias your random number:
$rnum = rand(90,110);
$rex1 = 45 - rand(0,90);
$rex2 = 45 - rand(0,90);
$rbias = $rnum + $rex1 + rex2;
This will increase likeliness of numbers around 100, numbers of 0-10 and 190-200 are quite unlikely while numbers between 80-120 are very likely.
create two ranges, one biased, one not.
not biased range: 0, 200
biased range: 80, 120
then pick a likeliness for getting a none biased number, say 20%. that's one 5th, or, 1 out of 5. then generate a random number between 1 and 5, if it comes out as 1, generate a random number from the none biased range. otherwise generate a number from the biased range.
since i started writing, a couple of answers has come in. the answer from rmflow describes my flow, but with a 36% likeliness of getting a none biased number.
good luck! :)
Just add spare capacity to the end of the range and anything above the maximum you shift into the biased range.
$rnum = rand(0,240);
if($rnum > 200) {
$rnum -= 120; // 201 -> 81
}
This will only double the probability of getting a value in that range. If you want more bias, extend the range further:
$rmax = 200;
$bmin = 80;
$bmax = 120;
$brange = $bmax - $bmin;
$nbias = 4;
$rnum = rand(0,$rmax+($brange*$nbias));
if($rnum > $rmax) {
$excess -= $rmax; // 201 -> 81
$remainder = modulo($rnum,$brange);
$rnum = $remainder+$bmin;
}
You can do it by defining an array of $item=>$weight:
$biasedArray = array(
'Blue' => 50,
'Yellow' => 30,
'Pink' => 10,
'Green' => 10,
);
chooseFromBiasedArray($biasArray);
function chooseFromBiasedArray($biasArray) {
$totalWeight = array_sum($biasedArray);
$randChoice = rand(1,$totalWeight);
$currentWeight = 0;
reset($biasedArray);
while ($currentWeight < $randChoice) {
$currentKey = key($biasedArray);
$currentWeight += $biasedArray[$currentKey];
if ($currentWeight < $randChoice) {
next($biasedArray);
}
}
//echo $randChoice . " -> " . current($biasArray);
return current($biasArray);
}
I wrote it very fast, you can do it much cleaner but the idea will be the same.
The answer by #rmflow would be the way in which I would add a weighted bias to a range. However, I would use the mt_rand function for better randomness.
mt_rand — Generate a random value via the Mersenne Twister Random Number Generator.
It is also purported to be 4x faster (This function produces a better random value, and is 4 times faster than rand()), however, in my experience I see little only a 20% increase in performance.
if (mt_rand(0, 10) > 7)
{
$rnum = mt_rand(80, 120);
}
else
{
$rnum = mt_rand(0, 200);
}
For an online game (MMORPG) I want to create characters (players) with random strength values. The stronger the characters are, the less should exist of this sort.
Example:
12,000 strength 1 players
10,500 strength 2 players
8,500 strength 3 players
6,000 strength 4 players
3,000 strength 5 players
Actually, I need floating, progressive strength values from 1.1 to 9.9 but for this example it was easier to explain it with integer strengths.
Do you have an idea how I could code this in PHP? Of course, I would need mt_rand() to generate random numbers. But how can I achieve this pyramid structure?
What function is it? Root function, exponential function, power function or logarithm function?
Thanks in advance!
It should look like this in a graph:
Pyramid graph http://img7.imageshack.us/img7/107/pyramidy.jpg
You can simulate a distribution such as the one you described using a logarithmic function. The following will return a random strength value between 1.1 and 9.9:
function getRandomStrength()
{
$rand = mt_rand() / mt_getrandmax();
return round(pow(M_E, ($rand - 1.033) / -0.45), 1);
}
Distribution over 1000 runs (where S is the strength value floored):
S | Count
--+------
1 - 290
2 - 174
3 - 141
4 - 101
5 - 84
6 - 67
7 - 55
8 - 50
9 - 38
Note:
This answer was updated to include a strength value of 1.1 (which wasn't included before because of the rounding) and to fix the name of the mt_getrandmax() function
The simplest way to do this would be to provide 'bands' for where a random number should go. In your example, you have 15 players so you could have:
rand < 1/15, highest strength
1/15 < rand < 3/15, second highest
3/15 < rand < 6/15, third highest
6/15 < rand < 10/15, fourth highest
10/15 < rand < 15/15, lowest strength
You could also parameterise such a function with a 'max' number of each band that you allow and when the band is filled, it is subsumed into the next lowest existing band (apart from the bottom band, which would be subsumed into the next highest) to ensure only a certain number of each with a random distribution.
Edit adding from my comments:
To get a floating range pyramid structure the best function would most likely be a logarithm. The formula:
11 - log10(rand)
would work (with log10 being a logarithm with base 10) as this would give ranges like:
1 < rand < 10 = 9 < strength < 10
10 < rand < 100 = 8 < strength < 9
100 < rand < 1000 = 7 < strength < 8
etc.
but rand would need to range from 1 to 10^10 which would require a lot of randomness (more than most random generators can manage). To get a random number in this sort of range you could multiply some together. 3 random numbers could manage it:
11 - log10(rand1 * rand2 * rand3)
with rand1 having range 1-10000 and rand2 and rand3 having range 1-1000. This would skew the distribution away from a proper pyramid slightly though (more likely to have numbers in the centre I believe) so it may not be suitable.
workmad3 has the start of it down, I think, but there's a catch - you need to track your bucket sizes and whether or not they're full. A random number generator won't guarantee that. You'll need to assign your bucket values (strenghs) and sizes (number of people), and let your random generator tell you which bucket to drop the player into - if that one is full, 'overflow' to the next lower.
As to assigning the bucket sizes for a given strength value, that's the tricky bit (and I think what you're really working at). The characteristics of your desired distribution are critical. If you want a linear drop (which the pyramid shape hints at), a line of the form
strength = max_strength - m(number_characters)
would work. Varying the value of m would change the speed at which the line drops off, and will basically limit your max number of total characters. If you're looking for a more sophisticated way for the strength values to drop off, you could use a parabolic or hyperbolic curve - these are a bit more complex, but give you very different characteristics.
something like this
<?php
$rand = rand(1,10);
switch ($rand) {
case 1:
echo "band 1";
break;
case 2:
case 3:
echo "band 2";
break;
case 4:
case 5:
case 6:
echo "band 3";
break;
default:
echo "band 4";
break;
}
?>
Band 1 being the strongest, band 4 being the weakest.
Ofcourse this is basic, you would want to refactor this to use loops instead of hardcoded switches, but you get the idea :)
It's probably easiest to use percentages in this case.
From your examples would approximately be (converted to an array for ease of use later):
$strength[1] = .3; // start with a key of 1
$strength[2] = .26;
$strength[3] = .21;
$strength[4] = .15;
$strength[5] = .08;
That way, you can generate a random number using mt_rand() and divide by the maximum possible value to get a number between 0 and 1:
$rand = mt_rand() / mt_getrandmax(); // rand is some random value between 0 and 1
Then you can use a foreach statement to isolate each case:
$comparisonPercentage = 1;
$selectedLevel = count($strength); // covers the case where mt_rand() returns 0
foreach($strength as $level => $currentPercentage)
{
$comparisonPercentage -= $currentPercentage;
if ($rand > $comparisonPercentage)
{
$selectedLevel = $level;
break;
}
}
// $selectedLevel contains the level you need...
If you do it this way, you only have to change the $strength array if you need to fiddle with the percentages.
generate a random number between 0 and 40000, if its between 0 and 12000, assign strength 1, between 12000 and 22500 assign 2 etc.
Edit: for progressive values between 0 and 10 use the square root of a random number between 0 and 100, then substract if from 10
rand -> strengh
0-1 -> 9.9 -> 9 (1%)
2-4 -> 9 -> 8 (2%)
...
81 - 100 -> 1 - 0 (19%)
For results between 1.1 and 9.9 the formula would be in pseudocode)
strength = 10 - sqrt(rand(1..79))