home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress2 / wp-includes / random_compat / random_int.php < prev    next >
Encoding:
PHP Script  |  2017-11-30  |  5.6 KB  |  194 lines

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