Completed
Push — master ( 20d8fe...40dd32 )
by Harald
09:20 queued 04:54
created

random_int.php ➔ random_int()   D

Complexity

Conditions 14
Paths 12

Size

Total Lines 153
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 45
nc 12
nop 2
dl 0
loc 153
rs 4.9516
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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