balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

random_int.php (6262B)


      1 <?php
      2 
      3 if (!is_callable('random_int')) {
      4     /**
      5      * Random_* Compatibility Library
      6      * for using the new PHP 7 random_* API in PHP 5 projects
      7      *
      8      * The MIT License (MIT)
      9      *
     10      * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
     11      *
     12      * Permission is hereby granted, free of charge, to any person obtaining a copy
     13      * of this software and associated documentation files (the "Software"), to deal
     14      * in the Software without restriction, including without limitation the rights
     15      * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     16      * copies of the Software, and to permit persons to whom the Software is
     17      * furnished to do so, subject to the following conditions:
     18      *
     19      * The above copyright notice and this permission notice shall be included in
     20      * all copies or substantial portions of the Software.
     21      *
     22      * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     23      * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     24      * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     25      * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     26      * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     27      * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     28      * SOFTWARE.
     29      */
     30 
     31     /**
     32      * Fetch a random integer between $min and $max inclusive
     33      *
     34      * @param int $min
     35      * @param int $max
     36      *
     37      * @throws Exception
     38      *
     39      * @return int
     40      */
     41     function random_int($min, $max)
     42     {
     43         /**
     44          * Type and input logic checks
     45          *
     46          * If you pass it a float in the range (~PHP_INT_MAX, PHP_INT_MAX)
     47          * (non-inclusive), it will sanely cast it to an int. If you it's equal to
     48          * ~PHP_INT_MAX or PHP_INT_MAX, we let it fail as not an integer. Floats
     49          * lose precision, so the <= and => operators might accidentally let a float
     50          * through.
     51          */
     52 
     53         try {
     54             $min = RandomCompat_intval($min);
     55         } catch (TypeError $ex) {
     56             throw new TypeError(
     57                 'random_int(): $min must be an integer'
     58             );
     59         }
     60 
     61         try {
     62             $max = RandomCompat_intval($max);
     63         } catch (TypeError $ex) {
     64             throw new TypeError(
     65                 'random_int(): $max must be an integer'
     66             );
     67         }
     68 
     69         /**
     70          * Now that we've verified our weak typing system has given us an integer,
     71          * let's validate the logic then we can move forward with generating random
     72          * integers along a given range.
     73          */
     74         if ($min > $max) {
     75             throw new Error(
     76                 'Minimum value must be less than or equal to the maximum value'
     77             );
     78         }
     79 
     80         if ($max === $min) {
     81             return (int) $min;
     82         }
     83 
     84         /**
     85          * Initialize variables to 0
     86          *
     87          * We want to store:
     88          * $bytes => the number of random bytes we need
     89          * $mask => an integer bitmask (for use with the &) operator
     90          *          so we can minimize the number of discards
     91          */
     92         $attempts = $bits = $bytes = $mask = $valueShift = 0;
     93 
     94         /**
     95          * At this point, $range is a positive number greater than 0. It might
     96          * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to
     97          * a float and we will lose some precision.
     98          */
     99         $range = $max - $min;
    100 
    101         /**
    102          * Test for integer overflow:
    103          */
    104         if (!is_int($range)) {
    105 
    106             /**
    107              * Still safely calculate wider ranges.
    108              * Provided by @CodesInChaos, @oittaa
    109              *
    110              * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435
    111              *
    112              * We use ~0 as a mask in this case because it generates all 1s
    113              *
    114              * @ref https://eval.in/400356 (32-bit)
    115              * @ref http://3v4l.org/XX9r5  (64-bit)
    116              */
    117             $bytes = PHP_INT_SIZE;
    118             $mask = ~0;
    119 
    120         } else {
    121 
    122             /**
    123              * $bits is effectively ceil(log($range, 2)) without dealing with
    124              * type juggling
    125              */
    126             while ($range > 0) {
    127                 if ($bits % 8 === 0) {
    128                     ++$bytes;
    129                 }
    130                 ++$bits;
    131                 $range >>= 1;
    132                 $mask = $mask << 1 | 1;
    133             }
    134             $valueShift = $min;
    135         }
    136 
    137         $val = 0;
    138         /**
    139          * Now that we have our parameters set up, let's begin generating
    140          * random integers until one falls between $min and $max
    141          */
    142         do {
    143             /**
    144              * The rejection probability is at most 0.5, so this corresponds
    145              * to a failure probability of 2^-128 for a working RNG
    146              */
    147             if ($attempts > 128) {
    148                 throw new Exception(
    149                     'random_int: RNG is broken - too many rejections'
    150                 );
    151             }
    152 
    153             /**
    154              * Let's grab the necessary number of random bytes
    155              */
    156             $randomByteString = random_bytes($bytes);
    157 
    158             /**
    159              * Let's turn $randomByteString into an integer
    160              *
    161              * This uses bitwise operators (<< and |) to build an integer
    162              * out of the values extracted from ord()
    163              *
    164              * Example: [9F] | [6D] | [32] | [0C] =>
    165              *   159 + 27904 + 3276800 + 201326592 =>
    166              *   204631455
    167              */
    168             $val &= 0;
    169             for ($i = 0; $i < $bytes; ++$i) {
    170                 $val |= ord($randomByteString[$i]) << ($i * 8);
    171             }
    172 
    173             /**
    174              * Apply mask
    175              */
    176             $val &= $mask;
    177             $val += $valueShift;
    178 
    179             ++$attempts;
    180             /**
    181              * If $val overflows to a floating point number,
    182              * ... or is larger than $max,
    183              * ... or smaller than $min,
    184              * then try again.
    185              */
    186         } while (!is_int($val) || $val > $max || $val < $min);
    187 
    188         return (int) $val;
    189     }
    190 }