I have a function where for a specific number range a percentage has to be applied:
0-100 EUR: 5%
>100 EUR: 3%
This number ranges and percentages should be user-defined.
The implementation should be as simple as possible.
What would you suggest for specifying the ranges and calculating the result?
Assuming you have access to some kind of database, I'd store the values there. You would need 3 tables at minimum:
ranges
id_range
from_number
until_number
percentage
users
id_user
user-ranges
id_range
id_user
Make the from_number and until_number columns nullable, to represent anything up to X and anything after Y.
Here's my short approach for calculating the result with defined rules in an array
$rules = array(
5 => '0-100 EUR', // here the percentages and their corresponding values
3 => '> 100 EUR'
);
$rand = mt_rand(0, 100); // calculate a random percent value
$lastPercentage = 0;
foreach($rules as $percentage => $val) {
// create a range from 0-5 (in the first iteration)
// every next iteration it is lastPercentage + 1 (5 + 1 in this case) to lastPercentage + current percentage
$range = range($lastPercentage, $percentage + $lastPercentage);
// yep, it is in the percentage range
if (in_array($rand, $range)) {
echo $lastPercentage,' < '.$rand.' > '.($percentage + $lastPercentage);
}
$lastPercentage = $percentage + 1; // +1 so that there are no overlapping ranges
}
Related
I want to generate range of numbers based on to total number of users in my database
<?php
//Let's assume the total number of users is 100
foreach( range(0, 100, 20) as $number){
$ranges=$number.'-'.($number+20);
echo'<button>'.$ranges.'</button>';
}
//Result:
0-20
20-40
40-60
60-80
80-100
100-120
?>
What i needed is 0-20, 21-40, 41-60, 61-80, 81-100.
I need to stop at 100 i.e the total number of users and not up to 100-120
or if you have a better approach
So two issues to deal with:
Stop at 100. Then you just decrease the loop's stop value a bit (e.g. reduce by 1, i.e. 99)
Start at one more than the previous range's end value, except for the first range which should start at 0. You can add !!$number to achieve that. This is a boolean which is true when $number is non-zero, and will coerce to a 0 or a 1, exactly what is needed.
Code:
foreach(range(0, 100 - 1, 20) as $number) {
$ranges = ($number + !!$number) . '-' . ($number + 20);
echo '<button>' . $ranges . '</button>';
}
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.
If I have an existing range:
1-10
11-50
Then I will enter a new range from 1 - 60, How could I detect that the new range to be added overlaps to the previous entries? And how can I get the available range? In this case the available range is from 51-60.
Does anyone here have a great idea on this?
Thanks for helping.
Here's my current code:
$saved = array(
array(
"start" => 1,
"end" => 10
),
array(
"start" => 10,
"end" => 50
)
);
$new_range = array(
"start" => 1,
"end" => 60
);
$usable_range = array();
$previous_from = 0;
$previous_to = 0;
foreach($saved as $value)
{
$range_start = 0;
$range_end = 0;
if($previous_from<$value['start'])
{
$previous_from = $value['start'];
}
if($previous_to<$value['end'])
{
$previous_to = $value['end'];
}
if($previous_from<=$new_range['start'])
{
if($previous_to<$new_range['end'])
{
$range_start = $previous_to+1;
$range_end = $new_range['end'];
$new_range['start'] = $range_start;
}
}
else if($previous_from>=$new_range['start'])
{
if($previous_to<$new_range['end'])
{
$range_start = $previous_to+1;
$range_end = $new_range['end'];
$new_range['start'] = $range_start;
}
}
$usable[] = array("range_start"=>$range_start,"range_end"=>$range_end);
}
Call every interval (min,max)
1) Sort your list of intervals by their min.
2) Check to see if any max is greater than the next entry over's min. If they are, create the interval that is the smaller of their mins and the greater of their maxes, and add it back into the list in place of those two.
3) Whenever you get a new interval, binary search into the list to add it, sorted by min. Then, using similar logic as in 2), attempt to merge it with the entry one below and one above until no more merges are possible.
EDIT: A few changes.
First, since we're using integers not floats, if you have an interval like 1,10 and 11,20 then there is no 'gap' between the 10 and 11 - so we should consider this to be one larger interval, 1,20. Reflect this by, instead of checking to see if max > next min, if max >= next min - 1.
Second, if you want to find all intervals formed by overlap when merging a new interval into your list of intervals:
1) Since your list of intervals is known to be in a state where it is sorted by min and also sorted by max, binary search into it to find the lowest min just above your new min and the highest max just above your new max.
2) Iterate over min, max, min, max... etc in your array that are between your new min and new max. Below the lowest min, above the highest max and between each max/min you can compute the interval that is in the 'gap' there, and return them in e.g. an array.
For example if your list of intervals contains 13,16, 21,24 and 31, 38 and you want to calculate the non-overlap of the new interval 1,30:
1) We binary search into our list and find that 13 is the lowest min above 1 and 24 is the highest max above 30.
2) We iterate like so:
Between our min and the lowest min is 1 and 13 - so this forms an interval 1,12 (inclusive bottom, exclusive top). Add onto the return array.
Between max and the next min is 16 and 21 - so this forms an interval 17,20 (exclusive on both ends). Add onto the return array.
Between max and our max is 24 and 30 - so this forms an interval 25,30 (exclusive bottom, inclusive top). Add onto the return array.
Finding overlap can be described as finding an intersection. Likewise finding the available range can be described as finding the difference. One way of doing this would be treating these sets as arrays and using the array_intersect [1] and array_diff [2] functions.
That is all I can come up with given your details.
[1] - http://php.net/manual/en/function.array-intersect.php
[2] - http://www.php.net/manual/en/function.array-diff.php
I need to total the number of clicks over 10 links on my page and then figure out the percentage of people that clicked each. This is easy division, but how do I make sure that I get a round 100% at the end.
I want to use the below code, but am worried that a situation could arise where the percentages do not tally to 100% as this function simply removes the numbers after the period.
function percent($num_amount, $num_total) {
$count1 = $num_amount / $num_total;
$count2 = $count1 * 100;
$count = number_format($count2, 0);
echo $count;
}
Any advice would be much appreciated.
Instead of calculating one percentage in your function you could pass all your results as an array and process it as a whole. After calculating all the percentages and rounding them make a check to see if they total 100. If not, then adjust the largest value to force them all to total 100. Adjusting the largest value will make sure your results are skewed as little as possible.
The array in my example would total 100.02 before making the adjustment.
function percent(array $numbers)
{
$result = array();
$total = array_sum($numbers);
foreach($numbers as $key => $number){
$result[$key] = round(($number/$total) * 100, 2);
}
$sum = array_sum($result);//This is 100.02 with my example array.
if(100 !== $sum){
$maxKeys = array_keys($result, max($result));
$result[$maxKeys[0]] = 100 - ($sum - max($result));
}
return $result;
}
$numbers = array(10.2, 22.36, 50.10, 27.9, 95.67, 3.71, 9.733, 4.6, 33.33, 33.33);
$percentages = percent($numbers);
var_dump($percentages);
var_dump(array_sum($percentages));
Output:-
array (size=10)
0 => float 3.51
1 => float 7.69
2 => float 17.22
3 => float 9.59
4 => float 32.86
5 => float 1.28
6 => float 3.35
7 => float 1.58
8 => float 11.46
9 => float 11.46
float 100
This will also work with an associative array as the function parameter. The keys will be preserved.
These figures could now be presented in a table, graph or chart and will always give you a total of 100%;
What you want to do is this.
Total the number of clicks across the board, then divide each number by the total.
For example:
1134
5391
2374
2887
In this case, four buttons, with a total of 11786 clicks, so:
1134 / 11786 = 0.09621....
5391 / 11786 = 0.45740....
2374 / 11786 = 0.20142....
2887 / 11786 = 0.24495....
Then for each division, round the result to 'two decimal points', so the first result:
0.09621.... becomes 0.10
because the 3rd point is 5 or above, it would remain at 0.09 if the 3rd point was below 5.
Once you have all of the results rounded, multiply each by 100 then add them up.
The ending result will always be 100.
Should warn you however that depending on how you use each individual percentage, when you round them, any result less that 0.05 will become 0%, unless you keep the value before you round it so you can declare it as a percentage less than 1.
I think you want to use ceil() or round() .
Since these are floating point numbers, there is room for error. Be careful how you round, and be sure that you don't independently calculate the last remaining percentages. Simply subtract the total of what you have from 1 or 100.
Make sure you dont calculate separate sides of the equation, sum one side, then subtract the other from 1 or 100 or however you are handling your percentages.
I run into this quite a bit and have a hack for it.
$percentages = array(
'1' => 87.5,
'2' => 12.5,
'3' => 0,
'4' => 0,
'5' => 0
);
If you round those percentages for output, you will end up with 88% and 13% (101%)
round($percentages['1']);
round($percentages['2']);
// 88
// 13
So here is the code I use to fix it.
$checkTotal = array_sum($percentages);
$max = max(array_keys($percentages));
if ($checkTotal > 100) {
$percentages[$max] = $percentages[$max] - 1;
}
if ($checkTotal < 100) {
$percentages[$max] = $percentages[$max] + 1;
}
If it is 100, do nothing.
If it is less than 100, add 1 to equal 100
If it is over 100, subtract 1 to equal 100
What I do
I am making graph of fictitious stock options.
The price is updated each second, with this function
function stockVariation($price,$max_up,$max_down)
{
// Price > 1
if($price > 1)
{
// Calculate
$ratio=(mt_rand(0,$max_up/2)-mt_rand(0,$max_down/2))/1000;
$price+=$ratio;
}
// Price <=1 (we don't want 0 or negative price...)
else
$price+=mt_rand(1,$max_up)/1000;
return round($price,3);
}
I use a max_up and max_down values (from 10 to 100) to make the price change progressively and simulate some volatility.
For example, with max_up : 40 and max_down : 45, the price will progressively go down.
My question
But the problem, is that prices generated are too much volatile, even if max_up = max_down.
The result is "non-natural". (for example +10 points in one day for a base price of 15,000).
Result of price evolution per hour in 24 hour
Perhaps making round($price,4) and divisions by 10 000 instead of 1 000, will be better ?
If anyone have an idea or an advice to generate "natural" prices evolution, thanks in advance.
There are 86400 seconds in a day, so you'll need to divide by a much larger number. And rather than adding and subtracting, you may want to multiply the current price by a factor that's slightly larger or smaller than 1. That would simulate a percentage increase or decrease, rather than an absolute gain or loss.
function stockVariation($price, $max_up, $max_down)
{
// Convert up/down to fractions of the current price.
// These will be very small positive numbers.
$random_up = mt_rand(0, $max_up) / $price;
$random_down = mt_rand(0, $max_down) / $price;
// Increase the price based on $max_up and decrease based on $max_down.
// This calculates the daily change that would result, which is slightly
// larger or smaller than 1.
$daily_change = (1 + $random_up) / (1 + $random_down);
// Since we're calling this function every second, we need to convert
// from change-per-day to change-per-second. This will make only a
// tiny change to $price.
$price = $price * $daily_change / 86400;
return round($price, 3);
}
Building upon the idea, you could use an actual volatility number. If you want e.g. a volatility of 35%/year, you can find the volatility per second. In pseudocode:
vol_yr = 0.35
vol_month = vol_yr * sqrt(1.0/12)
vol_second = vol_yr * sqrt(1.0/(252 * 86400)) # or 365 * 86400
Then, every second, you "flip a coin" and either multiply or divide current stock price by (1 + vol_second). This is the principle of how binomial trees are created to evaluate exotic stock options.