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;
}
}
?>
Related
First post, please be gentle.
I'm trying to create a simple market script where for example I have a number in my database ie 50.00 and I want to run a cron job php script to increase or decrease this randomly to a minimum of 10.00 and a maximum of 75.00.
I thought a random 0,1 follow by 2 if statements 1 rand(-0.01,0.05) if 2 rand(0.01,0.05) then $sql = "UPDATE price SET oil='RESULT'";
I've tried a few times at the above but I can't get it to run and the other crons in the file work.
<?php
//Get Oil Price from database
$oilchange = rand(1, 2);
if ($oilchange == '1') {
$oilnew = rand(0.01,0.05);
//Oil price from database times oil new.
} else {
$oilnew = rand(-0.01,-0.05);
//Oil price from database times oil new.
}
// Update Price
?>
Rand is for integers (whole numbers)
First up, your use of rand between two decimal values (called floats) won't work, as rand is for integers only. So, you'd first want to have a random function which does output floats, like this:
function randomFloat($min = 0, $max = 1) {
return $min + mt_rand() / mt_getrandmax() * ($max - $min);
}
Then we can safely use it between, say, 1% and 5%:
$percentSwing = randomFloat(0.01, 0.05);
Rand defaults to being 0 or 1. We can use that to randomly invert it, so we also cover -1% to -5%:
$percentSwing *= rand() ? 1 : -1;
The above could also be written like this:
if(rand() == 1){
// Do nothing:
$percentSwing *= 1;
}else{
// Invert it:
$percentSwing *= -1;
}
So, we now know how much we need to swing the number by. Let's say it was $oilPrice:
$oilPrice = 48;
We can just multiply the percent swing by that number to get the amount it's changing by, then add it back on:
$oilPrice += $percentSwing * $oilPrice;
So far so good! Now we need to make sure the price did not go out of our fixed range of 10 to 75. Assuming you want to 'clamp' the number - that means if it goes below 10, it's set at 10 and vice-versa, that's done like this:
if( $oilPrice < 10 ){
// It went below 10 - clamp it:
$oilPrice = 10;
}else if( $oilPrice > 75 ){
// It went above 75 - clamp it:
$oilPrice = 75;
}
The above can also be represented in one line, like this:
$oilPrice = max(10, min(75, $oilPrice));
So, that gives us the whole thing:
function randomFloat($min = 0, $max = 1) {
return $min + mt_rand() / mt_getrandmax() * ($max - $min);
}
// Define the oil price (e.g. pull from your database):
$oilPrice = 48;
// get a random 1% to 5% swing:
$percentSwing = randomFloat(0.01, 0.05);
// Invert it 50% of the time:
$percentSwing *= rand() ? 1 : -1;
// Swing the price now:
$oilPrice += $percentSwing * $oilPrice;
// Clamp it:
$oilPrice = max(10, min(75, $oilPrice));
// Output something!
echo $oilPrice;
As a side note here, money in real financial systems is never stored as a float, because rounding errors can cause major problems.
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
}
My question is how could I replace those if's with math formula?
if ($l <= 3500)
{
$min = 100;
}
elseif ($l <= 4000)
{
$min = 120;
}
elseif ($l <= 4500)
{
$min = 140;
}
elseif ($l <= 5000)
{
$min = 160;
}
As you see this is raising 20 for every 500 levels.
As you see this is raising 20 for every 500 levels.
Well, that's your formula right there.
$min = 100 + ceil(($l-3500)/500) * 20;
We start with 100, our base value and add that to the rest of the calculation.
$l starts with 3500 less.
We ceil() the result since we only want to jump when we pass the whole value.
We multiply that by 20.
If we want to address the case where $l is less than 3500 and set 100 as the minimum value, we also need to asset that $l-3500 is more than zero. We can do this as such:
$min = 100 + ceil(max(0,$l-3500)/500) * 20;
How did I get there?
What we're actually doing is plotting a line. Like you said yourself we go a constant amount for every constant amount. We have something called linear progression here.
Great, so we recognized the problem we're facing. We have an imaginary line to plot and we want integer values. What next? Well, let's see where the line starts?
In your case the answer is pretty straightforward.
if ($l <= 3500) {
$min = 100;
}
That's our starting point. So we know the point (3500,100) is on our line. This means the result starts at 100 and the origin starts at 3500.
We know that our formula is in the form of 100+<something>. What is that something?
Like you said, for every 500 levels you're raising 20. So we know we move 20/500 for every 1 level (because well, if we multiply that by 500 we get our original rule). We also know (from before) that we start from 3500.
Now, we might be tempted to use $min = 100 + ($l-3500) * (20/500); and that's almost right. The only problem here is that you only want integer values. This is why we ceil the value of level/500 to only get whole steps.
I tried to keep this with as little math terminology as possible, you can check the wikipedia page if you want things more formal. If you'd like any clarification - let me know
Here is my approach about this problem. It's not better than a single-line formula, but for sake of being modifiable, I generally decide this kind of solutions:
$min = 100;
for($i=3500; $i<=5000; $i+=500)
{
if($l <= $i) break;
$min += 20;
}
//Now $min has got desired value.
You can express the function as follows:
f(x) := a * x + b
The inclination of the line is calculated as:
a := 20 / 500
To find b you need to extrapolate a value that's on the line; in this case, that could be 3500 (x) and 120 (f(x)). That works out to be -40.
So the function has become:
f(x) := (20 / 500) * x - 40
There are two special cases:
Left of 3500 the value of f(x) must remain 100, even though f(x) is less.
The inclination is not continuous but discrete.
Both cases applied:
$min = max(100, ceil($l / 500) * 20 - 40)
Okay I have kind of a spin system, you spin and it generates a random number.
If the number is less than 100, you will win.
But how can I make it so, the lower the number his, the higher coins you will get
Currently i have this:
public function getPrize($number)
{
$prize = $number * 250 / 2;
if ($number < 100)
{
return '<span style="color: green;">You have won lucky <b>'.$prize.'</b> coins!</span>';
}
else
{
return '<span style="color: red;">Sorry but, bad luck. You have won nothing! number: '.$number.'</span>';
}
}
$prize is the prize. Basically now I am multi piling it by 250 and dividing by 2. so if I get the number '1'. i will get an awful prize.
How do I do that?
Solved it with a little of thinking and calculation.
1000 - 250 / 100 = 7.5
$prize = 250 + (750 - ($number * 7.5));
Results:
x(1) = 1000
x(100) = 250
Here is one way. Just takes the opposite of the number using a maximum.
function getNumPrizes($number)
{
$maxPrizes = 100;
// using max so we dont get less than 0
$prizesWon = max(($maxPrizes - $number) + 1, 0);
return $prizesWon;
}
This way you'll end up getting 100 coins for a 1, 99 for a 2, etc.
You could then run $prizesWon through another function to scale it how you wish.
Here is another solution.
function getNumPrizes($number)
{
$maxPrizes = 100;
$multiplier = // number you want to multiply with the result. It may be 125 or something else
// using max so we dont get less than 0
$prizesWon = max(($maxPrizes - $number) + 1, 0)*$multiplier;
return $prizesWon;
}
Maybe you'll try this way:
public function getPrize($number)
{
$quad = $number*$number;
if ($number<100 && $number>0)
{
$prize = 0.0525665*$quad-12.885*$number + 1012.83; //http://www.wolframalpha.com/input/?i=quadratic+fit+%281%2C1000%29%2C%2850%2C500%29%2C%28100%2C250%29
return '<span style="color: green;">You have won lucky <b>'.$prize.'</b> coins!</span>';
}
else
{
return '<span style="color: red;">Sorry but, bad luck. You have won nothing! number: '.$number.'</span>';
}
}
edit.
I've found a function which gives the expected results for the given numbers. Hope it's ok.
Data source: Wolphram Alpha
Final version:
if($number < 100){
$prize = round((99.00 - floatval($number)) * 7.653) + 250;
else{
$prize = 0;
}
This gives 250 for $number = 99 and 1000 for $number = 1 as author desires.
you are trying to define a math function f(x) where f(1) = 250 and f(99) = 1000;
there are lots of possible shapes which will.
i suggest you graph the results of your functions to help you decide what is best for you.
here are some examples.
// linear growth
// this produces a straight line
function prize($number) return (101 - number) * 75 + 250;
// log growth
// this produces a log curve where you have diminishing returns
function prize($number) return (log(101 - number) -1 ) * 750 + 250;
// exp growth
// this returns exponential returns
function prize($number) return (((101-number)^2)/10000) * 750 +250;
the basic operations here are you have a function which generates a values for the series between 1-100.
invert the input (101-number) so that smaller inputs produce bigger results.
map the output to your scale... which is between (0 to 750) by multipling 750 by a ratio.
translate your scaled number to 250 which is your minimum
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);
}