Replace elseifs with math - php

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)

Related

Get the percentage between 2 numbers

I am trying to figure out if there is a way to find the percentage between 2 numbers.
It's a progress / ranking system.
I want to find the percentage the $current_exp is between the $current_min and the $current_max values, is there a way to achieve this in PHP? So far I've got to this, but it doesn't work as you progress in ranks, it doesn't treat the $current_min as 0 so when you rank up, it says you are like 75% into your next rank progression when you're in fact 0. Does this make sense?
$currentProg = ($current_exp * 100) / $current_max;
Say the current minimum is 18750 and the current maximum is 25100, the current exp is 22000... What percentage from the min to the max is the current exp? This will change each rank as the $current_min and $current_max variables get set depending on the exp of the user.
The next rank is Current min is 25100 Current max is 34230
Currently, when you are at 26000 exp, the output is saying 75.956763073327% which is not correct, it should be like 1 or 2%?
Thanks in advance 🙏
Not a good mathematician, but it looks like it should be:
(Difference of rank - minimum) / (Difference of maximum - minimum) * 100
<?php
$x = 25100;
$z = 34230;
$y = 26000;
echo ($y - $x + 1) / ($z - $x + 1) * 100; // outputs 9.8674843938232 %
Online Demo
Note: + 1 is added to both numerator and denominator to avoid divide by zero errors.

What's the most efficient way of randomly picking a floating number within a specific range? [duplicate]

How does one generate a random float between 0 and 1 in PHP?
I'm looking for the PHP's equivalent to Java's Math.random().
You may use the standard function: lcg_value().
Here's another function given on the rand() docs:
// auxiliary function
// returns random number with flat distribution from 0 to 1
function random_0_1()
{
return (float)rand() / (float)getrandmax();
}
Example from documentation :
function random_float ($min,$max) {
return ($min+lcg_value()*(abs($max-$min)));
}
rand(0,1000)/1000 returns:
0.348 0.716 0.251 0.459 0.893 0.867 0.058 0.955 0.644 0.246 0.292
or use a bigger number if you want more digits after decimal point
class SomeHelper
{
/**
* Generate random float number.
*
* #param float|int $min
* #param float|int $max
* #return float
*/
public static function rand($min = 0, $max = 1)
{
return ($min + ($max - $min) * (mt_rand() / mt_getrandmax()));
}
}
update:
forget this answer it doesnt work wit php -v > 5.3
What about
floatVal('0.'.rand(1, 9));
?
this works perfect for me, and it´s not only for 0 - 1 for example between 1.0 - 15.0
floatVal(rand(1, 15).'.'.rand(1, 9));
function mt_rand_float($min, $max, $countZero = '0') {
$countZero = +('1'.$countZero);
$min = floor($min*$countZero);
$max = floor($max*$countZero);
$rand = mt_rand($min, $max) / $countZero;
return $rand;
}
example:
echo mt_rand_float(0, 1);
result: 0.2
echo mt_rand_float(3.2, 3.23, '000');
result: 3.219
echo mt_rand_float(1, 5, '00');
result: 4.52
echo mt_rand_float(0.56789, 1, '00');
result: 0.69
$random_number = rand(1,10).".".rand(1,9);
function frand($min, $max, $decimals = 0) {
$scale = pow(10, $decimals);
return mt_rand($min * $scale, $max * $scale) / $scale;
}
echo "frand(0, 10, 2) = " . frand(0, 10, 2) . "\n";
This question asks for a value from 0 to 1. For most mathematical purposes this is usually invalid albeit to the smallest possible degree. The standard distribution by convention is 0 >= N < 1. You should consider if you really want something inclusive of 1.
Many things that do this absent minded have a one in a couple billion result of an anomalous result. This becomes obvious if you think about performing the operation backwards.
(int)(random_float() * 10) would return a value from 0 to 9 with an equal chance of each value. If in one in a billion times it can return 1 then very rarely it will return 10 instead.
Some people would fix this after the fact (to decide that 10 should be 9). Multiplying it by 2 should give around a ~50% chance of 0 or 1 but will also have a ~0.000000000465% chance of returning a 2 like in Bender's dream.
Saying 0 to 1 as a float might be a bit like mistakenly saying 0 to 10 instead of 0 to 9 as ints when you want ten values starting at zero. In this case because of the broad range of possible float values then it's more like accidentally saying 0 to 1000000000 instead of 0 to 999999999.
With 64bit it's exceedingly rare to overflow but in this case some random functions are 32bit internally so it's not no implausible for that one in two and a half billion chance to occur.
The standard solutions would instead want to be like this:
mt_rand() / (getrandmax() + 1)
There can also be small usually insignificant differences in distribution, for example between 0 to 9 then you might find 0 is slightly more likely than 9 due to precision but this will typically be in the billionth or so and is not as severe as the above issue because the above issue can produce an invalid unexpected out of bounds figure for a calculation that would otherwise be flawless.
Java's Math.random will also never produce a value of 1. Some of this comes from that it is a mouthful to explain specifically what it does. It returns a value from 0 to less than one. It's Zeno's arrow, it never reaches 1. This isn't something someone would conventionally say. Instead people tend to say between 0 and 1 or from 0 to 1 but those are false.
This is somewhat a source of amusement in bug reports. For example, any PHP code using lcg_value without consideration for this may glitch approximately one in a couple billion times if it holds true to its documentation but that makes it painfully difficult to faithfully reproduce.
This kind of off by one error is one of the common sources of "Just turn it off and on again." issues typically encountered in embedded devices.
Solution for PHP 7. Generates random number in [0,1). i.e. includes 0 and excludes 1.
function random_float() {
return random_int(0, 2**53-1) / (2**53);
}
Thanks to Nommyde in the comments for pointing out my bug.
>>> number_format((2**53-1)/2**53,100)
=> "0.9999999999999998889776975374843459576368331909179687500000000000000000000000000000000000000000000000"
>>> number_format((2**53)/(2**53+1),100)
=> "1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
Most answers are using mt_rand. However, mt_getrandmax() usually returns only 2147483647. That means you only have 31 bits of information, while a double has a mantissa with 52 bits, which means there is a density of at least 2^53 for the numbers between 0 and 1.
This more complicated approach will get you a finer distribution:
function rand_754_01() {
// Generate 64 random bits (8 bytes)
$entropy = openssl_random_pseudo_bytes(8);
// Create a string of 12 '0' bits and 52 '1' bits.
$x = 0x000FFFFFFFFFFFFF;
$first12 = pack("Q", $x);
// Set the first 12 bits to 0 in the random string.
$y = $entropy & $first12;
// Now set the first 12 bits to be 0[exponent], where exponent is randomly chosen between 1 and 1022.
// Here $e has a probability of 0.5 to be 1022, 0.25 to be 1021, etc.
$e = 1022;
while($e > 1) {
if(mt_rand(0,1) == 0) {
break;
} else {
--$e;
}
}
// Pack the exponent properly (add four '0' bits behind it and 49 more in front)
$z = "\0\0\0\0\0\0" . pack("S", $e << 4);
// Now convert to a double.
return unpack("d", $y | $z)[1];
}
Please note that the above code only works on 64-bit machines with a Litte-Endian byte order and Intel-style IEEE754 representation. (x64-compatible computers will have this). Unfortunately PHP does not allow bit-shifting past int32-sized boundaries, so you have to write a separate function for Big-Endian.
You should replace this line:
$z = "\0\0\0\0\0\0" . pack("S", $e << 4);
with its big-endian counterpart:
$z = pack("S", $e << 4) . "\0\0\0\0\0\0";
The difference is only notable when the function is called a large amount of times: 10^9 or more.
Testing if this works
It should be obvious that the mantissa follows a nice uniform distribution approximation, but it's less obvious that a sum of a large amount of such distributions (each with cumulatively halved chance and amplitude) is uniform.
Running:
function randomNumbers() {
$f = 0.0;
for($i = 0; $i < 1000000; ++$i) {
$f += \math::rand_754_01();
}
echo $f / 1000000;
}
Produces an output of 0.49999928273099 (or a similar number close to 0.5).
I found the answer on PHP.net
<?php
function randomFloat($min = 0, $max = 1) {
return $min + mt_rand() / mt_getrandmax() * ($max - $min);
}
var_dump(randomFloat());
var_dump(randomFloat(2, 20));
?>
float(0.91601131712832)
float(16.511210331931)
So you could do
randomFloat(0,1);
or simple
mt_rand() / mt_getrandmax() * 1;
what about:
echo (float)('0.' . rand(0,99999));
would probably work fine... hope it helps you.

PHP - Changing Value by a small market percentage

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.

Checking a number's factor

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;
}
}
?>

Mapping increasing rank to decreasing but non-negative points

I need to create a business logic or php function to compute the following: given some input $rank (which is the alexa ranking) I need to compute some $points in such a way that $points will be high for the top ranking website and will decrease with increasing $rank value.
I imagine something like this:
function($rank)
{
$points = x*$rank;
return $points;
}
How do I get $points in such a way that
if the rank is 1 then the points returned is maximum (e.g. 10000).
if rank is 2 then $points returned will be 9500 or nearby.
if rank is 4 then $points returned will be 6000 or nearby.
if rank is 200 $points returned will be 2 or whatever the function will return.
Rule: if $rank is less then $points should be more. Maximal value of $points is 10000 which is for $rank=1.
Now as the $rank increases the $points value should decrease accordingly.
There are many formulas which might satisfy your requiremements.
Nested powers
One possibility:
$points = 10000 * pow(0.993575964272119, pow($rank, 3.16332422407427) - 1)
This gives you the following results:
f(1) = 10000
f(2) = 9500
f(4) = 6000
f(9) = 12.065
f(10) = 0.84341
f(200) = 0
So the three values you fixed (1, 2 and 4) are all satisfied, but the result for 200 indicates that this might not be exactly what you're looking for. The curve looks like this:
By the way, I found this using python and mpmath, by fixing the form of the formula and determining the numbers with the many digits numerically:
>>> import mpmath
>>> print(mpmath.findroot((lambda a,b: 10000*a**(2**b - 1) - 9500,
... lambda a,b: 10000*a**(4**b - 1) - 6000),
... (0.995, 2.7)))
[0.993575964272119]
[ 3.16332422407427]
If you decide on a different form of the function, this approach might be adapted.
Exp of a polynomial
A possible different form with the desired properties would be this:
$points = exp(9.14265175282929 + $rank*(0.127179575914116 - $rank*0.0594909567672230))
This does not decrease quite as quickly as the one above:
f( 1) = 10000
f( 2) = 9500
f( 4) = 6000
f( 13) = 2.1002
f( 14) = 0.47852
f(200) = 0
It was obtained by solving this system of equations:
a + b + c = log(10000)
a + 2b + 4c = log( 9500)
a + 4b + 16c = log( 6000)
to obtain the coefficients a through c for the polynomial. One can add another degree to match f(200)=2 as well, but in that case, the last coefficient will become positive, which means that points will start to increase with rank for very large ranks.
If you want to match that f(200)=2 as well, you can do so using
$points = exp(max(8.86291000469285 - $rank*0.0408488141206645,
9.14265175282929 + $rank*(0.127179575914116 - $rank*0.0594909567672230)))
although this will result in a bend in your curve.
To compare these alternatives to the above:
function getPoints($rank)
{
$returnValue = -0.005 * $rank * $rank - 0.035 * $rank + 100.040;
if ($returnValue < 0) $returnValue = 0;
return $returnValue;
}
This was my thinking.
Function is not forking for large values:
it should atleast give some small value for large ranks...
like if rank is 2000000 then points will be 2.
Thnx btw

Categories