IntMath::identify()   A
last analyzed

Complexity

Conditions 6
Paths 5

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 8
cts 8
cp 1
rs 9.1111
c 0
b 0
f 0
cc 6
nc 5
nop 1
crap 6
1
<?php
2
3
/**
4
 * This file is part of the phpcommon/intmath package.
5
 *
6
 * (c) Marcos Passos <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE file
9
 * that was distributed with this source code.
10
 */
11
12
namespace PhpCommon\IntMath;
13
14
use InvalidArgumentException;
15
16
// Only available on PHP >= 7
17 2
if (!defined('PHP_INT_MIN')) {
18
    define('PHP_INT_MIN', -\PHP_INT_MAX - 1);
19
}
20
21
/**
22
 * Utility class for arithmetic operations on integers that wraps around upon
23
 * overflow.
24
 *
25
 * **Important**
26
 *
27
 * This class is not intended for use as a way to properly perform arithmetic
28
 * operations on integers and should not be used in place of the native
29
 * arithmetic operators or any other library designed for such purpose.
30
 *
31
 * Unlike other languages that overflow large positive integers into large
32
 * negative integers, PHP actually overflows integers to floating-point
33
 * numbers. In most cases, arithmetic overflows must be treated as an unusual
34
 * circumstance which requires special handling. However, there are some cases
35
 * where such _wrap-around_ behavior is actually useful - for example with TCP
36
 * sequence numbers or certain algorithms, such as hash calculation. This
37
 * utility class provides basic arithmetic functions that operate in accordance
38
 * to that behaviour.
39
 *
40
 * To illustrate, consider the following example:
41
 *
42
 * ```php
43
 * // Output on 64-bit system: float(9.2233720368548E+18)
44
 * var_dump(PHP_MAX_INT + 1);
45
 *
46
 * // Output on 64-bit system: int(-9223372036854775808)
47
 * var_dump(IntMath::add(PHP_MAX_INT, 1));
48
 * ```
49
 *
50
 * As previously shown, adding one to the largest supported integer using
51
 * native arithmetic operators will result in a floating-point number. By
52
 * contrast, using {@link IntMath::add()} will cause an overflow, resulting
53
 * in the smallest integer supported in this build of PHP.
54
 *
55
 * @author Marcos Passos <[email protected]>
56
 */
57
class IntMath
58
{
59
    /**
60
     * Returns the negation of the argument.
61
     *
62
     * For integer values, negation is the same as subtraction from zero.
63
     * Because PHP uses two's-complement representation for integers and the
64
     * range of two's-complement values is not symmetric, the negation of the
65
     * maximum negative integer results in that same maximum negative number.
66
     * Despite the fact that overflow has occurred, no exception is thrown.
67
     *
68
     * For all integer values `$a`, `-$a` equals `(~$a) + 1`.
69
     *
70
     * @param integer $a The value.
71
     *
72
     * @return integer The result.
73
     *
74
     * @throws InvalidArgumentException If the argument is not an integer.
75
     */
76 26
    public static function negate($a)
77
    {
78 26
        self::assertInteger($a);
79
80 4
        if ($a === \PHP_INT_MIN) {
81 2
            return $a;
82
        }
83
84 2
        return -$a;
85
    }
86
87
    /**
88
     * Returns the sum of the arguments.
89
     *
90
     * The result is the low-order bits of the true mathematical result as
91
     * represented in a sufficiently wide two's-complement format. If overflow
92
     * occurs, then the sign of the result may not be the same as the sign of
93
     * the mathematical sum of the two values. Despite the overflow, no
94
     * exception is thrown in this case.
95
     *
96
     * @param integer $a The addend.
97
     * @param integer $b The addend.
98
     *
99
     * @return integer The sum.
100
     *
101
     * @throws InvalidArgumentException If one of the arguments is not an
102
     *                                  integer.
103
     */
104 26
    public static function add($a, $b)
105
    {
106 26
        self::assertInteger($a);
107 4
        self::assertInteger($b);
108
109 4
        if (($b > 0) && ($a <= (\PHP_INT_MAX - $b))) {
110 2
            return $a + $b;
111
        }
112
113 4
        if (($b < 0) && ($a >= (\PHP_INT_MIN - $b))) {
114 2
            return $a + $b;
115
        }
116
117 4
        while ($b !== 0) {
118
            // Carry now contains common set bits of the addends
119 2
            $carry = $a & $b;
120
            // Sum of bits of $x and $y,
121
            // where at least one of the bits is not set
122 2
            $a ^= $b;
123
            // Left-shift by one
124 2
            $b = $carry << 1;
125 2
        }
126
127 4
        return $a;
128
    }
129
130
    /**
131
     * Returns the difference of the arguments.
132
     *
133
     * The subtraction of a positive number yields the same result as the
134
     * addition of a negative number of equal magnitude. Furthermore, the
135
     * subtraction from zero is the same as negation.
136
     *
137
     * The result is the low-order bits of the true mathematical result as
138
     * represented in a sufficiently wide two's-complement format. If overflow
139
     * occurs, then the sign of the result may not be the same as the sign of
140
     * the mathematical difference of the two values. Despite the overflow, no
141
     * exception is thrown in this case.
142
     *
143
     * @param integer $a The minuend.
144
     * @param integer $b The subtrahend.
145
     *
146
     * @return integer The difference.
147
     *
148
     * @throws InvalidArgumentException If one of the arguments is not an
149
     *                                  integer.
150
     */
151 26
    public static function subtract($a, $b)
152
    {
153 26
        self::assertInteger($a);
154 4
        self::assertInteger($b);
155
156 4
        return self::add((int)$a, self::negate($b));
157
    }
158
159
    /**
160
     * Returns the product of the arguments.
161
     *
162
     * The result is the low-order bits of the true mathematical result as
163
     * represented in a sufficiently wide two's-complement format. If overflow
164
     * occurs, then the sign of the result may not be the same as the sign of
165
     * the mathematical product of the two values. Despite the overflow, no
166
     * exception is thrown in this case.
167
     *
168
     * @param integer $a The multiplicand.
169
     * @param integer $b The multiplier.
170
     *
171
     * @return integer The product.
172
     *
173
     * @throws InvalidArgumentException If one of the arguments is not an
174
     *                                  integer.
175
     */
176 26
    public static function multiply($a, $b)
177
    {
178 26
        self::assertInteger($a);
179 4
        self::assertInteger($b);
180
181 4
        if ($a === 0 || $b === 0) {
182 2
            return 0;
183
        }
184
185
        // If the multiplicand or the multiplier are the smallest integer
186
        // supported, then the product is `0` or the smallest integer supported,
187
        // according as the other operand is odd or even respectively.
188 4
        if ($a === \PHP_INT_MIN) {
189 2
            return $b & 0x01 ? $a : 0;
190
        }
191
192 4
        if ($b === \PHP_INT_MIN) {
193 4
            return $a & 0x01 ? $b : 0;
194
        }
195
196 4
        $max = \PHP_INT_MIN;
197
198
        // Same sign
199 4
        if ($a >= 0 && $b >= 0 || $a < 0 && $b < 0) {
200 4
            $max = \PHP_INT_MAX;
201 4
        }
202
203
        // Use native operators unless the operation causes an overflow
204 4
        if (($b > 0 && $b <= ($max / $a)) || ($b < 0 && $b >= ($max / $a))) {
205 2
            return $a * $b;
206
        }
207
208
        // Signed multiplication can be accomplished by doing an unsigned
209
        // multiplication and taking manually care of the negative-signs.
210 2
        $sign = \false;
211
212 2
        if ($a < 0) {
213
            // Toggle the signed flag
214 2
            $sign = !$sign;
215
            // Negate $a
216 2
            $a = self::negate($a);
217 2
        }
218
219 2
        if ($b < 0) {
220
            // Toggle the signed flag
221 2
            $sign = !$sign;
222
            // Negate $b
223 2
            $b = self::negate($b);
224 2
        }
225
226 2
        $product = 0;
227
        // Both operands are now positive, perform unsigned multiplication
228 2
        while ($a !== 0) {
229
            // Test the least significant bit (LSB) of multiplier
230 2
            if (($a & 0x01) !== 0) {
231
                // If 1, add the multiplicand to the product
232 2
                $product = self::add($product, $b);
233 2
            }
234
235
            // Left-shift by one, or divide by 2
236 2
            $a >>= 1;
237
238
            // Right-shift by one, or multiply by 2
239 2
            $b <<= 1;
240 2
        }
241
242 2
        if ($sign) {
243
            // Negate the product
244 2
            $product = self::negate($product);
245 2
        }
246
247 2
        return $product;
248
    }
249
250
    /**
251
     * Returns the quotient of the arguments.
252
     *
253
     * The division rounds the result towards zero. Thus the absolute value of
254
     * the result is the largest possible integer that is less than or equal to
255
     * the absolute value of the quotient of the two operands. The result is
256
     * zero or positive when the two operands have the same sign and zero or
257
     * negative when the two operands have opposite signs.
258
     *
259
     * There is one special case that does not satisfy this rule: if the
260
     * dividend is the negative integer of largest possible magnitude for its
261
     * type, and the divisor is `-1`, then integer overflow occurs and the
262
     * result is equal to the dividend. Despite the overflow, no exception is
263
     * thrown in this case. On the other hand if the value of the divisor in an
264
     * integer division is `0`, then a `DivisionByZeroException` is thrown.
265
     *
266
     * @param integer $a The dividend.
267
     * @param integer $b The divisor.
268
     *
269
     * @return integer The quotient.
270
     *
271
     * @throws InvalidArgumentException If one of the arguments is not an
272
     *                                  integer.
273
     * @throws \DivisionByZeroError  If the divisor is zero.
274
     */
275 30
    public static function divide($a, $b)
276
    {
277 30
        self::assertInteger($a);
278 8
        self::assertInteger($b);
279
280 8
        if (0 === $b) {
281 2
            throw new \DivisionByZeroError('Division by zero.');
282
        }
283
284 6
        if ($a === \PHP_INT_MIN && $b === -1) {
285 2
            return $a;
286
        }
287
288 4
        return ($a - ($a % $b)) / $b;
289
    }
290
291
    /**
292
     * Asserts the specified value is an integer.
293
     *
294
     * @param mixed $value The value to assert.
295
     *
296
     * @throws InvalidArgumentException If the value is not an integer.
297
     */
298 112
    private static function assertInteger($value)
299
    {
300 112
        if (\is_int($value)) {
301 2
            return;
302
        }
303
304 110
        throw new InvalidArgumentException(\sprintf(
305 110
            'Expected an integer, but got "%s"',
306 110
            self::identify($value)
307 110
        ));
308
    }
309
310
    /**
311
     * Returns a string that identifies the specified value.
312
     *
313
     * @param mixed $value The value to identify.
314
     *
315
     * @return string A string that identifies the specified value.
316
     */
317 110
    private static function identify($value)
318
    {
319 110
        if (!\is_numeric($value) || !\is_float($value)) {
320 70
            return \gettype($value);
321
        }
322
323 40
        if (\is_infinite($value)) {
324 20
            return $value < 0 ? '-INF' : 'INF';
325
        }
326
327 20
        if (\is_nan($value)) {
328 10
            return 'NAN';
329
        }
330
331 10
        return 'float';
332
    }
333
}
334