[ Index ] |
PHP Cross Reference of WordPress |
[Summary view] [Print] [Text view]
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 - 2018 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 /** @var int $min */ 55 $min = RandomCompat_intval($min); 56 } catch (TypeError $ex) { 57 throw new TypeError( 58 'random_int(): $min must be an integer' 59 ); 60 } 61 62 try { 63 /** @var int $max */ 64 $max = RandomCompat_intval($max); 65 } catch (TypeError $ex) { 66 throw new TypeError( 67 'random_int(): $max must be an integer' 68 ); 69 } 70 71 /** 72 * Now that we've verified our weak typing system has given us an integer, 73 * let's validate the logic then we can move forward with generating random 74 * integers along a given range. 75 */ 76 if ($min > $max) { 77 throw new Error( 78 'Minimum value must be less than or equal to the maximum value' 79 ); 80 } 81 82 if ($max === $min) { 83 return (int) $min; 84 } 85 86 /** 87 * Initialize variables to 0 88 * 89 * We want to store: 90 * $bytes => the number of random bytes we need 91 * $mask => an integer bitmask (for use with the &) operator 92 * so we can minimize the number of discards 93 */ 94 $attempts = $bits = $bytes = $mask = $valueShift = 0; 95 /** @var int $attempts */ 96 /** @var int $bits */ 97 /** @var int $bytes */ 98 /** @var int $mask */ 99 /** @var int $valueShift */ 100 101 /** 102 * At this point, $range is a positive number greater than 0. It might 103 * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to 104 * a float and we will lose some precision. 105 * 106 * @var int|float $range 107 */ 108 $range = $max - $min; 109 110 /** 111 * Test for integer overflow: 112 */ 113 if (!is_int($range)) { 114 115 /** 116 * Still safely calculate wider ranges. 117 * Provided by @CodesInChaos, @oittaa 118 * 119 * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435 120 * 121 * We use ~0 as a mask in this case because it generates all 1s 122 * 123 * @ref https://eval.in/400356 (32-bit) 124 * @ref http://3v4l.org/XX9r5 (64-bit) 125 */ 126 $bytes = PHP_INT_SIZE; 127 /** @var int $mask */ 128 $mask = ~0; 129 130 } else { 131 132 /** 133 * $bits is effectively ceil(log($range, 2)) without dealing with 134 * type juggling 135 */ 136 while ($range > 0) { 137 if ($bits % 8 === 0) { 138 ++$bytes; 139 } 140 ++$bits; 141 $range >>= 1; 142 /** @var int $mask */ 143 $mask = $mask << 1 | 1; 144 } 145 $valueShift = $min; 146 } 147 148 /** @var int $val */ 149 $val = 0; 150 /** 151 * Now that we have our parameters set up, let's begin generating 152 * random integers until one falls between $min and $max 153 */ 154 /** @psalm-suppress RedundantCondition */ 155 do { 156 /** 157 * The rejection probability is at most 0.5, so this corresponds 158 * to a failure probability of 2^-128 for a working RNG 159 */ 160 if ($attempts > 128) { 161 throw new Exception( 162 'random_int: RNG is broken - too many rejections' 163 ); 164 } 165 166 /** 167 * Let's grab the necessary number of random bytes 168 */ 169 $randomByteString = random_bytes($bytes); 170 171 /** 172 * Let's turn $randomByteString into an integer 173 * 174 * This uses bitwise operators (<< and |) to build an integer 175 * out of the values extracted from ord() 176 * 177 * Example: [9F] | [6D] | [32] | [0C] => 178 * 159 + 27904 + 3276800 + 201326592 => 179 * 204631455 180 */ 181 $val &= 0; 182 for ($i = 0; $i < $bytes; ++$i) { 183 $val |= ord($randomByteString[$i]) << ($i * 8); 184 } 185 /** @var int $val */ 186 187 /** 188 * Apply mask 189 */ 190 $val &= $mask; 191 $val += $valueShift; 192 193 ++$attempts; 194 /** 195 * If $val overflows to a floating point number, 196 * ... or is larger than $max, 197 * ... or smaller than $min, 198 * then try again. 199 */ 200 } while (!is_int($val) || $val > $max || $val < $min); 201 202 return (int) $val; 203 } 204 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Jul 9 01:00:02 2023 | Cross-referenced by PHPXref 0.7.1 |