Skip to content

Instantly share code, notes, and snippets.

@vip3r011
Last active November 14, 2020 09:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vip3r011/c8001f129d6d93b7c303888457ea04e3 to your computer and use it in GitHub Desktop.
Save vip3r011/c8001f129d6d93b7c303888457ea04e3 to your computer and use it in GitHub Desktop.
distribution
<?php
function randomInt(int $min, int $max) :int
{
if (!is_int($min)) {
throw new \Exception('First parameter ($min) must be an integer');
}
if (!is_int($max)) {
throw new \Exception('Second parameter ($max) must be an integer');
}
if ($min > $max) {
throw new \Exception('First parameter ($min) must be no greater than second parameter ($max)');
}
if ($min === $max) {
return $min;
}
// $range is a PHP float if the expression exceeds PHP_INT_MAX.
$range = $max - $min + 1;
if (is_float($range)) {
$mask = null;
} else {
// Make a bit mask of (the next highest power of 2 >= $range) minus one.
$mask = 1;
$shift = $range;
while ($shift > 1) {
$shift >>= 1;
$mask = ($mask << 1) | 1;
}
}
$tries = 0;
do {
$bytes = random_bytes(PHP_INT_SIZE);
// Convert byte string to a signed int by shifting each byte in.
$value = 0;
for ($pos = 0; $pos < PHP_INT_SIZE; $pos += 1) {
$value = ($value << 8) | ord($bytes[$pos]);
}
if ($mask === null) {
// Use all bits in $bytes and check $value against $min and $max instead of $range.
if ($value >= $min && $value <= $max) {
return $value;
}
} else {
// Use only enough bits from $bytes to cover the $range.
$value &= $mask;
if ($value < $range) {
return $value + $min;
}
}
$tries += 1;
} while ($tries < 123);
// Worst case: this is as likely as 123 heads in as many coin tosses.
throw new \Exception('Unable to generate random int after 123 tries');
}
/*source: https://stackoverflow.com/questions/40644040/how-to-generate-a-random-number-from-a-uniform-distribution-in-php
just simulating dice rolls here
*/
function testRand($randFunction, $groupsNumber = 6, $rollsNumber = 4000000)
{
$frequencies = array_fill(0, $groupsNumber, 0);
foreach (range(1, $rollsNumber) as $ignored) {
$frequencies[$randFunction(0, $groupsNumber - 1)]++;
}
echo PHP_EOL, "------- results for `$randFunction` -------", PHP_EOL;
$cum = 0;
foreach ($frequencies as $index => $frequency) {
$percent = $frequency * 100 / $rollsNumber;
$cum += $percent;
echo sprintf("%d\t|\t%4d\t%6.2f\t%6.2f",
$index, $frequency, $percent, $cum),
PHP_EOL;
}
}
testRand('randomInt');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment