PHP random - exluding/include/floats/negatives and other animals - php

I need to generate a random pairs of numbers (floats) , within a certain range .
Basically those are points for [Lat,Lng] pairs (Longitude - latitude coordinates)
I thought This would be very straight forward with
<?php echo (rand(0,60).'.'.rand(0,99999999999999).' || '); // yes, a string is ok...?>
but it did not give me the control over how many numbers after the float point (resolution) - which I need fixed.
So the next phase was :
<?php echo (rand(0,60*pow(10,6)))/pow(10,6).' || '; //pow(10,$f) where $ is the resolution ?>
and this works . sort of ..
sometimes it produces results like
22.212346 || 33.134 || 36.870757 || //(rare , but does happen)
but hey - coordinates are from -90 to 90 (lon) and -180 to 180 (lan) - what about the minus ?
echo (rand(0,-180*pow(10,9)))/pow(10,9).' || ';
that should do it .. and combining all together should give me somehow a random string like
23.0239423525 || -135.937419777
so after all this introduction - here is Are my question(s) .
Being the newbie that I am - am I missing something ? is there no built-in function to generate random floats with a negative to positive range in PHP ?
Why is the function above sometimes turns only resolution 3,4 or 5 if it is told to return 6 (i did not apply any ABS or ROUND) - is there an automatic rounding in php ? and if there is , how to avoid it ?
I have noticed that the "random" is not so random - the generated numbers are always more or less series between a range - close to one another . is the PHP random a simple very-very-very fast rotating counter ?
how do I EXCLUDE a range from this generated range ?? (or actually and array of ranges)
I know these are a lot of questions, but any help / thought would be great ! (and if the first one is answered positively, the rest can almost be ignored :-)

The rand() function can take a negative min so maybe you can do this:
$num = mt_rand(-180000000, 180000000)/1000000;
echo number_format($num, 6);
if you want 6 places after decimal point.
In terms of excluding a range you may have to do it in two steps. First consider the ranges that you do want. Lets say you have 3 ranges from which you want to generate the random number. range 1 = -180 to -10, range 2 = 10 - 100 and range 3 = 120 - 180. Then you can generate a random number from 1 to 3 inclusive, use that to pick one range and then generate the number in that range.

Related

How to Generate random number within specific number

I need to generate three different random numbers without repeating, Three different random numbers need to be within 10 of the answer
for the sample IQ Question: 4,6 ,9,6,14,6,... Ans:19
A: random numbers
B: random numbers
C: random numbers
D: random numbers
one of them is the answer
I am now using the following code but sometimes the numbers are repeated, I have tried shuffle But which one is really random cannot satisfy random numbers need to be within 10 of the answer
$ans = $row['answer'];
$a = rand (1,10);
$a1 = rand($ans-$a ,$ans+$a);
$a2 = rand($ans-$a ,$ans+$a);
$a3 = rand($ans-$a ,$ans+$a);
As shown in previous answers (e.g. Generating random numbers without repeats, Simple random variable php without repeat, Generating random numbers without repeats) you can use shuffle to randomise a range, and then pick three items using array_slice.
The difference in your case is how you define the range:
Rather than 1 to 10, you want $ans - 10 to $ans + 10
You want to exclude the right answer
One way to build that is as two ranges: lower limit up to but not including right answer, and right answer + 1 up to upper limit.
function generate_wrong_answers($rightAnswer) {
// Generate all wrong guesses from 10 below to 10 above,
// but miss out the correct answer
$wrongAnswers = array_merge(
range($rightAnswer - 10, $rightAnswer - 1),
range($rightAnswer + 1, $rightAnswer + 10)
);
// Randomise
shuffle($wrongAnswers);
// Pick 3
return array_slice($wrongAnswers, 0, 3);
}

Format a user inputted number into a common "Engine Size" pattern

I'm trying to format a given number into a simple pattern in PHP, used for a vehicle Engine Size display.
The pattern needs to be [digit][dot][digit] - i.e. 1.8 or 2.0 or 0.7
I have zero control over validation of the data that is entered. I can only use what is given. However, it could follow any of these patterns:
1, 1.2, 1600, 1998.9, 1920
Given the examples above, here is what I would need to return:
1 = 1.0
1.2 = 1.2
1600 = 1.6
1998.9 = 2.0
1920 = 1.9
I've tried playing with round(), floor() and number_format() but I can't quite figure out a way to 'catch-all' given possibilities.
I could probably sit down and write a huge function to get this working, but I'm not too knowledgeable on optimising my PHP - so just wondering if there's a graceful solution that could exist?
We can abuse/exploit PHP's loose typing for this, then with some Regex "magic" a pinch of number formatting and 67bytes - ad {waves hands} vola'
$inputs = [1, 1.2, 1600, 1998.9, 1920];
foreach($inputs as $input)
echo number_format(preg_replace(['/\./','/^(\d)/'],['','\1.'],$input),1)."\n";
Output
1.0
1.2
1.6
2.0
1.9
Sandbox
Basically what I am doing is
'/\./' replace . with ''
remove dots
'/^(\d)/' capture the first digit (group 1)
replace this match with itself (the first digit) and a dot \1.
Format it with 1 decimal place
Basically it places the decimal after the first number (no matter where it was), then rounds it to 1 place. So for 1998.9
remove dots 19989
add dot after first digit 1.9989
format 2.0
Because PHP is loosely typed it doesn't care if its a number like string or a number. So we can just shift the decimal were we want it and PHP could care less ... lol ... if it looks like a number, and smells like a number it must be a number.
P.S. There are of course many ways to do the above, but this preg_replace was the first one I thought of that I could do in 1 line.
Cheers!
UPDATE
Here is one (golfed a bit) that doesn't use Regex
$inputs = [1, 1.2, 1600, 1998.9, 1920];
foreach($inputs as $input)
echo number_format((($i=str_replace(".","",$input))?substr($i,0,1).".".substr($i,1):$i),1)."\n";
Sandbox
I'm sure someone with better math skills can come up with a cleaner solution than using a while loop, but this does what you ask for:
function formatNumber($num)
{
// We divide the number until it's less than 10
while ($num >= 10) {
$num = $num / 10;
}
// Now we round the number to one decimal precision.
return round($num, 1);
}
Here's a demo

PHP 7 intdiv() vx floor or custom type cast

I am looking PHP 7 new Additions in order to improve my skills, i want to know what exactly the difference between the intdiv() vs floor() or custom type cast
for example
echo (int) (8/3); // 2
echo floor((8/3)); // 2
echo intdiv((8/3)); // 2
what exactly the reason for this function to add in newer version of PHP.
(int) casts the value to int whereas floor() keeps a float as a float. You used positive numbers in your example, but the difference is in negative numbers.
intdivwas designed to keep int after division of 2 ints.
echo (int) (-8/3); // -2
echo floor(-8/3); // -3
echo intdiv(-8,3); //-2
There is a different in how they behave in terms of rounding.
divint() is more intelligent, it always knows if it supposes to round up or down.
floor()
With floor(), you will always round the value lover so:
3.33333 becomes 3
-3.33333 becomes -4
Which is often not what you intend.
divint()
divint will round to the closest number:
3.33333 becomes 3
-3.33333 becomes -3
Casting to (int)
Casting float/double to a int will perform similary like divint. So you can use:
(int)(10/3) returns 3
(int)(-10/3) returns -3
In general all these three you mentioned works similar as of your example but intdiv() will give you more accurate result while working extremely large set of numbers
here is example you can see.
echo PHP_INT_MAX; // 9223372036854775807
echo (int)(PHP_INT_MAX/2); // 4611686018427387904
// here you can look the ending number
echo floor(PHP_INT_MAX/2); // 4.6116860184274E+18
// here you can see floor will return scientific notation for larger numbers
echo intdiv(PHP_INT_MAX,2); // 4611686018427387903
// you can compare the result return by (int) cast
intdiv() always give you positive number or in other word intdiv() let you know how many times you can divide evenly
Another example developer always use Modulus Operator in order to get remainder but intdiv() will always return you positive numbers and let you know how many times you can divide evenly.
echo (5 % 2) // 1
echo intdiv(5, 2) // 2
Hope this good enough to understand the difference among all 3 of them..

Two-way hashing of fixed range numbers

I need to create a function which takes a single integer as argument in the range 0-N and returns a seemingly random number in the same range.
Each input number should always have exactly one output and it should always be the same.
Such a function would produce something like this:
f(1) = 4
f(2) = 1
f(3) = 5
f(4) = 2
f(5) = 3
I believe this could be accomplished by some kind of a hashing algorithm? I don't need anything complex, just not something too simple like f(1) = 2, f(2) = 3 etc.
The biggest issue is that I need this to be reversible. E.g. the above table should be true left-to-right as well as right-to-left, using a different function for the right-to-left conversion is fine.
I know the easiest way is to create an array, shuffle it and just store the relations in a db or something, but as I need N to be quite large I'd like to avoid this if possible.
Edit: For my particular case N is a specific number, it's exactly 16777216 (64^4).
If the range is always a power of two -- like [0,16777216) -- then you can use exclusive-or just as #MarkBaker suggested. It just doesn't work so easily if your range is not a power of two.
You can use addition and subtraction modulo N, although these alone are too obvious, so you have to combine it with something else.
You can also do multiplication modulo-N, but reversing that is complicated. To make it simpler, we can isolate the bottom eight bits and multiply those and add them in a way that doesn't interfere with those bits so we can use them again to reverse the operation.
I don't know PHP so I'm going to give an example in C, instead. Maybe it's the same.
int enc(int x) {
x = x + 4799 * 256 * (x % 256);
x = x + 8896843;
x = x ^ 4777277;
return (x + 1073741824) % 16777216;
}
And to decode, play the operations back in reverse order:
int dec(int x) {
x = x + 1073741824;
x = x ^ 4777277;
x = x - 8896843;
x = x - 4799 * 256 * (x % 256);
return x % 16777216;
}
That 1073741824 must be a multiple of N, and 256 must be a factor of N, and if N is not a power of two then you can't (necessarily) use exclusive-or (^ is exclusive-or in C and I assume in PHP too). The other numbers you can fiddle with, and add and remove stages, at your leisure.
The addition of 1073741824 in both functions is to ensure that x stays positive; this is so that the modulo operation doesn't ever give a negative result, even after we've subtracted values from x which might have made it go negative in the interim.
I offered to describe how I "randomly" scramble up 9-digit SSNs when producing research data sets. This does not replace or hash an SSN. It re-orders the digits. It is difficult to put the digits back in the correct order if you don't know the order in which they were scrambled. I have a gut feeling that this is not what the questioner really wants. So, I am happy to delete this answer if it is deemed off-topic.
I know that I have 9 digits. So, I start with an array that has 9 index values in order:
$a = array(0,1,2,3,4,5,6,7,8);
Now, I need to turn a key that I can remember into a way to shuffle the array. The shuffling has to be the same order for the same key every time. I use a couple tricks. I use crc32 to turn a word into a number. I use srand/rand to get a predictable order of random values. Note: mt_rand no longer produces the same sequence of random digits with the same seed, so I have to use rand.
srand(crc32("My secret key"));
usort($a, function($a, $b) { return rand(-1,1); });
The array $a still has the digits 0 through 8, but they are shuffled. If I use the same keyword I will get the same shuffled order every time. That lets me repeat this every month and get the same result. Then, with a shuffled array, I can pick the digits off the SSN. First, I ensure it has 9 characters (some SSNs are sent as integers and a leading 0 is omitted). Then, I build a masked SSN by picking the digits using $a.
$ssn = str_pad($ssn, 9, '0', STR_PAD_LEFT);
$masked_ssn = '';
foreach($a as $i) $masked_ssn.= $ssn{$i};
$masked_ssn will now have all the digits in $ssn, but in a different order. Technically, there are keywords that make $a become the original ordered array after shuffling, but that is very very rare.
Hopefully this makes sense. If so, you can do it all much faster. If you turn the original string into an array of characters, you can shuffle the array of characters. You just need to reseed rand every time.
$ssn = "111223333"; // Assume I'm using a proper 9-digit SSN
$a = str_split($ssn);
srand(crc32("My secret key"));
usort($a, function($a, $b) { return rand(-1,1); });
$masked_ssn = implode('', $a);
This is not really faster in a runtime way because rand is a rather expensive function and you run rand a hell of lot more here. If you are masking thousands of values as I do, you will want to use an index array that is shuffled just once, not a shuffling for every value.
Now, how do I undo it? Assume I'm using the first method with the index array. It will be something like $a = {5, 3, 6, 1, 0, 2, 7, 8, 4}. Those are the indexes for the original SSN in the masked order. So, I can easily build the original SSN.
$ssn = '000000000'; // I like to define all 9 characters before I start
foreach($a as $i=>$j) $ssn[$j] = $masked_ssn{$i};
As you can see, $i counts from 0 to 8 across the masked SSN. $j counts 5, 3, 6... and puts each value from the masked SSN in the correct place in the original SSN.
Looks like you've got good answer, but still there is an alternative. Linear Congruential Generator (LCG) could provide 1-to-1 mapping and it is known to be a reversible using Euclid's algorithm. For 24bit
Xi = [(A * Xi-1) + C] Mod M
where M = 2^24 = 16,777,216
A = 16,598,013
C = 12,820,163
For LCG reversability take a look at Reversible pseudo-random sequence generator

10 digit mt_rand() with unbiased first digit

I want to generate the profile ids in my software. The mt_rand function works well but I need the ids to be a fixed 10 digit long. Currently I am looping through mt_rand outputs until I get a 10 digit number. But the problem I am facing now is that most of the profile ids start from 1 and some from 2. None from any of the other single digit numbers. I understand this happens because of mt_rand's range and it can't produce 10 digit numbers that start with 3 or more.
This is what I am currently doing
for($i = 0; $i < 200; $i++){
$num = mt_rand();
if(strlen($num) == 10) echo $num."<br>";
}
If you run the above code you will see all numbers start from either 1 or 2. Any way to fix this?
Edit: I guess I can just flip the numbers but some numbers end with zero and this seems like a bit of a hack anyways. But then again, random number generation is a hack in itself I guess.
just start your IDs at 1000000001 , then ID 2 at 1000000002 , ID 543 at 1000000543 , and so on?
alternatively, keep calling mt_rand(1000000001,min((PHP_INT_SIZE>4 ? intval("9999999999",10): PHP_INT_MAX),mt_getrandmax())) until you get an ID which does not already exist in your database? (this will be more and more cpu intesive as your db grows larger and larger.. when its almost full, i wouldn't be surprised if it took billions of iterations and several minutes..)
To elaborate on Rizier's suggestion, the only way to ensure any string (even a string of numbers) fits a given mold for length and rules is to generate it one character at a time and then fit them together
$str = '';
for($loop = 0; $loop < 10; $loop++) {
$str .= mt_rand(0,9);
}
echo $str;
You can then add rules to this. Maybe you don't want a leading 0 so you can add a rule for that. Maybe you want letters too. This will always give you a random string with the rules you want.
You can see this in action here http://3v4l.org/kIRdV

Categories