2016-04-20 09:56:34 -07:00
< ? php
2017-04-13 15:53:35 +01:00
if ( ! is_callable ( 'random_int' )) {
2016-04-20 09:56:34 -07:00
/**
2017-04-13 15:53:35 +01:00
* Random_ * Compatibility Library
* for using the new PHP 7 random_ * API in PHP 5 projects
*
* The MIT License ( MIT )
*
2018-11-23 12:29:20 +00:00
* Copyright ( c ) 2015 - 2018 Paragon Initiative Enterprises
2017-04-13 15:53:35 +01:00
*
* Permission is hereby granted , free of charge , to any person obtaining a copy
* of this software and associated documentation files ( the " Software " ), to deal
* in the Software without restriction , including without limitation the rights
* to use , copy , modify , merge , publish , distribute , sublicense , and / or sell
* copies of the Software , and to permit persons to whom the Software is
* furnished to do so , subject to the following conditions :
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software .
*
* THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
* IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER
* LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING FROM ,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE .
2016-04-20 09:56:34 -07:00
*/
/**
2017-04-13 15:53:35 +01:00
* Fetch a random integer between $min and $max inclusive
*
* @ param int $min
* @ param int $max
*
* @ throws Exception
*
* @ return int
2016-04-20 09:56:34 -07:00
*/
2017-04-13 15:53:35 +01:00
function random_int ( $min , $max )
{
2016-04-20 09:56:34 -07:00
/**
2017-04-13 15:53:35 +01:00
* Type and input logic checks
*
* If you pass it a float in the range ( ~ PHP_INT_MAX , PHP_INT_MAX )
* ( non - inclusive ), it will sanely cast it to an int . If you it ' s equal to
* ~ PHP_INT_MAX or PHP_INT_MAX , we let it fail as not an integer . Floats
* lose precision , so the <= and => operators might accidentally let a float
* through .
2016-04-20 09:56:34 -07:00
*/
2017-04-13 15:53:35 +01:00
try {
2018-11-23 12:29:20 +00:00
/** @var int $min */
2017-04-13 15:53:35 +01:00
$min = RandomCompat_intval ( $min );
} catch ( TypeError $ex ) {
throw new TypeError (
'random_int(): $min must be an integer'
);
2016-04-20 09:56:34 -07:00
}
2017-04-13 15:53:35 +01:00
try {
2018-11-23 12:29:20 +00:00
/** @var int $max */
2017-04-13 15:53:35 +01:00
$max = RandomCompat_intval ( $max );
} catch ( TypeError $ex ) {
throw new TypeError (
'random_int(): $max must be an integer'
2016-04-20 09:56:34 -07:00
);
}
/**
2017-04-13 15:53:35 +01:00
* Now that we ' ve verified our weak typing system has given us an integer ,
* let ' s validate the logic then we can move forward with generating random
* integers along a given range .
2016-04-20 09:56:34 -07:00
*/
2017-04-13 15:53:35 +01:00
if ( $min > $max ) {
throw new Error (
'Minimum value must be less than or equal to the maximum value'
2016-04-20 09:56:34 -07:00
);
}
2017-04-13 15:53:35 +01:00
if ( $max === $min ) {
2018-11-23 12:29:20 +00:00
return ( int ) $min ;
2017-04-13 15:53:35 +01:00
}
2016-04-20 09:56:34 -07:00
/**
2017-04-13 15:53:35 +01:00
* Initialize variables to 0
*
* We want to store :
* $bytes => the number of random bytes we need
* $mask => an integer bitmask ( for use with the & ) operator
* so we can minimize the number of discards
2016-04-20 09:56:34 -07:00
*/
2017-04-13 15:53:35 +01:00
$attempts = $bits = $bytes = $mask = $valueShift = 0 ;
2018-11-23 12:29:20 +00:00
/** @var int $attempts */
/** @var int $bits */
/** @var int $bytes */
/** @var int $mask */
/** @var int $valueShift */
2016-04-20 09:56:34 -07:00
/**
2017-04-13 15:53:35 +01:00
* At this point , $range is a positive number greater than 0. It might
* overflow , however , if $max - $min > PHP_INT_MAX . PHP will cast it to
* a float and we will lose some precision .
2018-11-23 12:29:20 +00:00
*
* @ var int | float $range
2016-04-20 09:56:34 -07:00
*/
2017-04-13 15:53:35 +01:00
$range = $max - $min ;
2016-04-20 09:56:34 -07:00
/**
2017-04-13 15:53:35 +01:00
* Test for integer overflow :
2016-04-20 09:56:34 -07:00
*/
2017-04-13 15:53:35 +01:00
if ( ! is_int ( $range )) {
/**
* Still safely calculate wider ranges .
* Provided by @ CodesInChaos , @ oittaa
*
* @ ref https :// gist . github . com / CodesInChaos / 03 f9ea0b58e8b2b8d435
*
* We use ~ 0 as a mask in this case because it generates all 1 s
*
* @ ref https :// eval . in / 400356 ( 32 - bit )
* @ ref http :// 3 v4l . org / XX9r5 ( 64 - bit )
*/
$bytes = PHP_INT_SIZE ;
2018-11-23 12:29:20 +00:00
/** @var int $mask */
2017-04-13 15:53:35 +01:00
$mask = ~ 0 ;
} else {
/**
* $bits is effectively ceil ( log ( $range , 2 )) without dealing with
* type juggling
*/
while ( $range > 0 ) {
if ( $bits % 8 === 0 ) {
++ $bytes ;
}
++ $bits ;
$range >>= 1 ;
2018-11-23 12:29:20 +00:00
/** @var int $mask */
2017-04-13 15:53:35 +01:00
$mask = $mask << 1 | 1 ;
}
$valueShift = $min ;
}
2018-11-23 12:29:20 +00:00
/** @var int $val */
2017-04-13 15:53:35 +01:00
$val = 0 ;
/**
* Now that we have our parameters set up , let ' s begin generating
* random integers until one falls between $min and $max
*/
2018-11-23 12:29:20 +00:00
/** @psalm-suppress RedundantCondition */
2017-04-13 15:53:35 +01:00
do {
/**
* The rejection probability is at most 0.5 , so this corresponds
* to a failure probability of 2 ^- 128 for a working RNG
*/
if ( $attempts > 128 ) {
throw new Exception (
'random_int: RNG is broken - too many rejections'
);
}
/**
* Let ' s grab the necessary number of random bytes
*/
$randomByteString = random_bytes ( $bytes );
/**
* Let ' s turn $randomByteString into an integer
*
* This uses bitwise operators ( << and | ) to build an integer
* out of the values extracted from ord ()
*
* Example : [ 9 F ] | [ 6 D ] | [ 32 ] | [ 0 C ] =>
* 159 + 27904 + 3276800 + 201326592 =>
* 204631455
*/
$val &= 0 ;
for ( $i = 0 ; $i < $bytes ; ++ $i ) {
$val |= ord ( $randomByteString [ $i ]) << ( $i * 8 );
}
2018-11-23 12:29:20 +00:00
/** @var int $val */
2017-04-13 15:53:35 +01:00
/**
* Apply mask
*/
$val &= $mask ;
$val += $valueShift ;
++ $attempts ;
/**
* If $val overflows to a floating point number ,
* ... or is larger than $max ,
* ... or smaller than $min ,
* then try again .
*/
} while ( ! is_int ( $val ) || $val > $max || $val < $min );
2018-11-23 12:29:20 +00:00
return ( int ) $val ;
2017-04-13 15:53:35 +01:00
}
2016-04-20 09:56:34 -07:00
}