Passed
Push — release-2.1 ( 41509f...c9f0cf )
by Mathias
06:43
created

random_int()   C

Complexity

Conditions 13
Paths 10

Size

Total Lines 162
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 42
c 0
b 0
f 0
nc 10
nop 2
dl 0
loc 162
rs 6.6166

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
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
}
205