Passed
Push — master ( f4e940...140d71 )
by kacper
03:07
created

BC   F

Complexity

Total Complexity 128

Size/Duplication

Total Lines 709
Duplicated Lines 0 %

Test Coverage

Coverage 98.39%

Importance

Changes 5
Bugs 2 Features 1
Metric Value
eloc 346
c 5
b 2
f 1
dl 0
loc 709
ccs 366
cts 372
cp 0.9839
rs 2
wmc 128

46 Methods

Rating   Name   Duplication   Size   Complexity  
A floor() 0 12 3
A parseNumber() 0 8 3
A dechex() 0 10 2
A add() 0 12 2
A rand() 0 9 1
A comp() 0 13 2
A convertScientificNotationToString() 0 16 4
A bin2dec() 0 14 2
A max() 0 13 4
A sqrt() 0 11 2
B powMod() 0 35 8
C bitOperatorHelper() 0 50 12
A alignBinLength() 0 3 1
A dec2bin() 0 17 3
A formatTrailingZeroes() 0 13 3
A isFloat() 0 3 1
A setTrimTrailingZeroes() 0 3 1
A exp() 0 9 2
A isNegative() 0 3 1
A parseValues() 0 7 2
A abs() 0 9 2
A min() 0 13 4
A powFractional() 0 12 1
A roundHalfEven() 0 26 6
A addTrailingZeroes() 0 16 4
A getScale() 0 11 2
A log() 0 27 5
A trimTrailingZeroes() 0 7 3
A hexdec() 0 10 2
A decBaseHelper() 0 13 3
A setScale() 0 3 1
A pow() 0 14 3
A mod() 0 13 1
A mul() 0 12 2
A fact() 0 17 4
A getDecimalsLength() 0 7 2
A bitXor() 0 3 1
A sub() 0 12 2
A div() 0 20 4
A bitAnd() 0 5 1
A bitOr() 0 3 1
A recalculateNegative() 0 13 3
A roundUp() 0 21 3
A roundDown() 0 21 3
A ceil() 0 12 3
A round() 0 12 3

How to fix   Complexity   

Complex Class

Complex classes like BC often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use BC, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace BCMathExtended;
6
7
use Closure;
8
use InvalidArgumentException;
9
use UnexpectedValueException;
10
11
class BC
12
{
13
    public const COMPARE_EQUAL = 0;
14
    public const COMPARE_LEFT_GRATER = 1;
15
    public const COMPARE_RIGHT_GRATER = -1;
16
17
    protected const DEFAULT_SCALE = 100;
18
19
    protected const MAX_BASE = 256;
20
21
    protected const BIT_OPERATOR_AND = 'and';
22
    protected const BIT_OPERATOR_OR = 'or';
23
    protected const BIT_OPERATOR_XOR = 'xor';
24
25
    protected static $trimTrailingZeroes = true;
26
27 2
    public static function rand(string $min, string $max): string
28
    {
29 2
        $max = static::convertScientificNotationToString($max);
30 2
        $min = static::convertScientificNotationToString($min);
31
32 2
        $difference = static::add(static::sub($max, $min), '1');
33 2
        $randPercent = static::div((string)mt_rand(), (string)mt_getrandmax(), 8);
34
35 2
        return static::add($min, static::mul($difference, $randPercent, 8), 0);
36
    }
37
38 399
    public static function convertScientificNotationToString(string $number): string
39
    {
40
        // check if number is in scientific notation, first use stripos as is faster than preg_match
41 399
        if (false !== stripos($number, 'E') && preg_match('/(-?(\d+\.)?\d+)E([+-]?)(\d+)/i', $number, $regs)) {
42
            // calculate final scale of number
43 76
            $scale = $regs[4] + static::getDecimalsLength($regs[1]);
44 76
            $pow = static::pow('10', $regs[4], $scale);
45 76
            if ('-' === $regs[3]) {
46 31
                $number = static::div($regs[1], $pow, $scale);
47
            } else {
48 45
                $number = static::mul($pow, $regs[1], $scale);
49
            }
50 76
            $number = static::formatTrailingZeroes($number, $scale);
51
        }
52
53 399
        return static::parseNumber($number);
54
    }
55
56 116
    public static function getDecimalsLength(string $number): int
57
    {
58 116
        if (static::isFloat($number)) {
59 106
            return strcspn(strrev($number), '.');
60
        }
61
62 11
        return 0;
63
    }
64
65 363
    protected static function isFloat(string $number): bool
66
    {
67 363
        return false !== strpos($number, '.');
68
    }
69
70 170
    public static function pow(string $base, string $exponent, ?int $scale = null): string
71
    {
72 170
        $base = static::convertScientificNotationToString($base);
73 170
        $exponent = static::convertScientificNotationToString($exponent);
74
75 170
        if (static::isFloat($exponent)) {
76 9
            $r = static::powFractional($base, $exponent, $scale);
77 162
        } elseif (null === $scale) {
78 78
            $r = bcpow($base, $exponent);
79
        } else {
80 87
            $r = bcpow($base, $exponent, $scale);
81
        }
82
83 170
        return static::formatTrailingZeroes($r, $scale);
84
    }
85
86 9
    protected static function powFractional(string $base, string $exponent, ?int $scale = null): string
87
    {
88
        // we need to increased scale to get correct results and avoid rounding error
89 9
        $currentScale = $scale ?? static::getScale();
90 9
        $increasedScale = $currentScale * 2;
91
92
        // add zero to trim scale
93 9
        return static::parseNumber(
94 9
            static::add(
95 9
                static::exp(static::mul($exponent, static::log($base), $increasedScale)),
96 9
                '0',
97 9
                $currentScale
98
            )
99
        );
100
    }
101
102 40
    public static function getScale(): int
103
    {
104 40
        if (PHP_VERSION_ID >= 70300) {
105
            /** @noinspection PhpStrictTypeCheckingInspection */
106
            /** @noinspection PhpParamsInspection */
107
            return bcscale();
108
        }
109
110 40
        $sqrt = static::sqrt('2');
111
112 40
        return strlen(substr($sqrt, strpos($sqrt, '.') + 1));
113
    }
114
115 45
    public static function sqrt(string $number, ?int $scale = null): string
116
    {
117 45
        $number = static::convertScientificNotationToString($number);
118
119 45
        if (null === $scale) {
120 41
            $r = bcsqrt($number);
121
        } else {
122 6
            $r = bcsqrt($number, $scale);
123
        }
124
125 45
        return static::formatTrailingZeroes($r, $scale);
126
    }
127
128 312
    protected static function formatTrailingZeroes(string $number, ?int $scale = null): string
129
    {
130 312
        if (self::$trimTrailingZeroes) {
131 312
            return static::trimTrailingZeroes($number);
132
        }
133
134
        // newer version of php correct add trailing zeros
135 1
        if (PHP_VERSION_ID >= 70300) {
136
            return $number;
137
        }
138
139
        // old one not so much
140 1
        return self::addTrailingZeroes($number, $scale);
141
    }
142
143 337
    protected static function trimTrailingZeroes(string $number): string
144
    {
145 337
        if (static::isFloat($number)) {
146 249
            $number = rtrim($number, '0');
147
        }
148
149 337
        return rtrim($number, '.') ?: '0';
150
    }
151
152 1
    protected static function addTrailingZeroes(string $number, ?int $scale): string
153
    {
154 1
        if (null === $scale) {
155 1
            return $number;
156
        }
157
158 1
        $decimalLength = static::getDecimalsLength($number);
159 1
        if ($scale === $decimalLength) {
160 1
            return $number;
161
        }
162
163 1
        if (0 === $decimalLength) {
164 1
            $number .= '.';
165
        }
166
167 1
        return str_pad($number, strlen($number) + ($scale - $decimalLength), '0', STR_PAD_RIGHT);
168
    }
169
170 399
    protected static function parseNumber(string $number): string
171
    {
172 399
        $number = str_replace('+', '', filter_var($number, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION));
173 399
        if ('-0' === $number || !is_numeric($number)) {
174 31
            return '0';
175
        }
176
177 388
        return $number;
178
    }
179
180 183
    public static function add(string $leftOperand, string $rightOperand, ?int $scale = null): string
181
    {
182 183
        $leftOperand = static::convertScientificNotationToString($leftOperand);
183 183
        $rightOperand = static::convertScientificNotationToString($rightOperand);
184
185 183
        if (null === $scale) {
186 41
            $r = bcadd($leftOperand, $rightOperand);
187
        } else {
188 145
            $r = bcadd($leftOperand, $rightOperand, $scale);
189
        }
190
191 183
        return static::formatTrailingZeroes($r, $scale);
192
    }
193
194 17
    public static function exp(string $number): string
195
    {
196 17
        $scale = static::DEFAULT_SCALE;
197 17
        $result = '1';
198 17
        for ($i = 299; $i > 0; --$i) {
199 17
            $result = static::add(static::mul(static::div($result, (string)$i, $scale), $number, $scale), '1', $scale);
200
        }
201
202 17
        return $result;
203
    }
204
205 184
    public static function mul(string $leftOperand, string $rightOperand, ?int $scale = null): string
206
    {
207 184
        $leftOperand = static::convertScientificNotationToString($leftOperand);
208 184
        $rightOperand = static::convertScientificNotationToString($rightOperand);
209
210 184
        if (null === $scale) {
211 55
            $r = bcmul($leftOperand, $rightOperand);
212
        } else {
213 132
            $r = bcmul($leftOperand, $rightOperand, $scale);
214
        }
215
216 184
        return static::formatTrailingZeroes($r, $scale);
217
    }
218
219 145
    public static function div(string $dividend, string $divisor, ?int $scale = null): string
220
    {
221 145
        $dividend = static::convertScientificNotationToString($dividend);
222 145
        $divisor = static::convertScientificNotationToString($divisor);
223
224 145
        if ('0' === static::trimTrailingZeroes($divisor)) {
225 1
            throw new InvalidArgumentException('Division by zero');
226
        }
227
228 144
        if (null === $scale) {
229 42
            $r = bcdiv($dividend, $divisor);
230
        } else {
231 111
            $r = bcdiv($dividend, $divisor, $scale);
232
        }
233
234 144
        if (null === $r) {
235
            throw new UnexpectedValueException('bcdiv should not return null!');
236
        }
237
238 144
        return static::formatTrailingZeroes($r, $scale);
239
    }
240
241 14
    public static function log(string $number): string
242
    {
243 14
        $number = static::convertScientificNotationToString($number);
244 14
        if ($number === '0') {
245 1
            return '-INF';
246
        }
247 13
        if (static::COMPARE_RIGHT_GRATER === static::comp($number, '0')) {
248 1
            return 'NAN';
249
        }
250 12
        $scale = static::DEFAULT_SCALE;
251 12
        $m = (string)log((float)$number);
252 12
        $x = static::sub(static::div($number, static::exp($m), $scale), '1', $scale);
253 12
        $res = '0';
254 12
        $pow = '1';
255 12
        $i = 1;
256
        do {
257 12
            $pow = static::mul($pow, $x, $scale);
258 12
            $sum = static::div($pow, (string)$i, $scale);
259 12
            if ($i % 2 === 1) {
260 12
                $res = static::add($res, $sum, $scale);
261
            } else {
262 11
                $res = static::sub($res, $sum, $scale);
263
            }
264 12
            ++$i;
265 12
        } while (static::comp($sum, '0', $scale));
266
267 12
        return static::add($res, $m, $scale);
268
    }
269
270 66
    public static function comp(string $leftOperand, string $rightOperand, ?int $scale = null): int
271
    {
272 66
        $leftOperand = static::convertScientificNotationToString($leftOperand);
273 66
        $rightOperand = static::convertScientificNotationToString($rightOperand);
274
275 66
        if (null === $scale) {
276 54
            return bccomp($leftOperand, $rightOperand, max(strlen($leftOperand), strlen($rightOperand)));
277
        }
278
279 24
        return bccomp(
280 24
            $leftOperand,
281 24
            $rightOperand,
282 24
            $scale
283
        );
284
    }
285
286 83
    public static function sub(string $leftOperand, string $rightOperand, ?int $scale = null): string
287
    {
288 83
        $leftOperand = static::convertScientificNotationToString($leftOperand);
289 83
        $rightOperand = static::convertScientificNotationToString($rightOperand);
290
291 83
        if (null === $scale) {
292 44
            $r = bcsub($leftOperand, $rightOperand);
293
        } else {
294 42
            $r = bcsub($leftOperand, $rightOperand, $scale);
295
        }
296
297 83
        return static::formatTrailingZeroes($r, $scale);
298
    }
299
300 1
    public static function setTrimTrailingZeroes(bool $flag): void
301
    {
302 1
        self::$trimTrailingZeroes = $flag;
303 1
    }
304
305 1
    public static function max(...$values): ?string
306
    {
307 1
        $max = null;
308 1
        foreach (static::parseValues($values) as $number) {
309 1
            $number = static::convertScientificNotationToString((string)$number);
310 1
            if (null === $max) {
311 1
                $max = $number;
312 1
            } elseif (static::comp($max, $number) === static::COMPARE_RIGHT_GRATER) {
0 ignored issues
show
Bug introduced by
$max of type void is incompatible with the type string expected by parameter $leftOperand of BCMathExtended\BC::comp(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

312
            } elseif (static::comp(/** @scrutinizer ignore-type */ $max, $number) === static::COMPARE_RIGHT_GRATER) {
Loading history...
313 1
                $max = $number;
314
            }
315
        }
316
317 1
        return $max;
318
    }
319
320 2
    protected static function parseValues(array $values): array
321
    {
322 2
        if (is_array($values[0])) {
323 2
            $values = $values[0];
324
        }
325
326 2
        return $values;
327
    }
328
329 1
    public static function min(...$values): ?string
330
    {
331 1
        $min = null;
332 1
        foreach (static::parseValues($values) as $number) {
333 1
            $number = static::convertScientificNotationToString((string)$number);
334 1
            if (null === $min) {
335 1
                $min = $number;
336 1
            } elseif (static::comp($min, $number) === static::COMPARE_LEFT_GRATER) {
0 ignored issues
show
Bug introduced by
$min of type void is incompatible with the type string expected by parameter $leftOperand of BCMathExtended\BC::comp(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

336
            } elseif (static::comp(/** @scrutinizer ignore-type */ $min, $number) === static::COMPARE_LEFT_GRATER) {
Loading history...
337 1
                $min = $number;
338
            }
339
        }
340
341 1
        return $min;
342
    }
343
344 14
    public static function powMod(
345
        string $base,
346
        string $exponent,
347
        string $modulus,
348
        ?int $scale = null
349
    ): string {
350 14
        $base = static::convertScientificNotationToString($base);
351 14
        $exponent = static::convertScientificNotationToString($exponent);
352
353 14
        if (static::isNegative($exponent)) {
354 2
            throw new InvalidArgumentException('Exponent can\'t be negative');
355
        }
356
357 12
        if ('0' === static::trimTrailingZeroes($modulus)) {
358
            throw new InvalidArgumentException('Modulus can\'t be zero');
359
        }
360
361
        // bcpowmod don't support floats
362
        if (
363 12
            static::isFloat($base) ||
364 9
            static::isFloat($exponent) ||
365 12
            static::isFloat($modulus)
366
        ) {
367 5
            $r = static::mod(static::pow($base, $exponent, $scale), $modulus, $scale);
368 7
        } elseif (null === $scale) {
369 1
            $r = bcpowmod($base, $exponent, $modulus);
370
        } else {
371 6
            $r = bcpowmod($base, $exponent, $modulus, $scale);
372
        }
373
374 12
        if (null === $r) {
375
            throw new UnexpectedValueException('bcpowmod should not return null!');
376
        }
377
378 12
        return static::formatTrailingZeroes($r);
379
    }
380
381 182
    protected static function isNegative(string $number): bool
382
    {
383 182
        return 0 === strncmp('-', $number, 1);
384
    }
385
386 55
    public static function mod(string $dividend, string $divisor, ?int $scale = null): string
387
    {
388 55
        $dividend = static::convertScientificNotationToString($dividend);
389
390
        // bcmod in 7.2 is not working properly - for example bcmod(9.9999E-10, -0.00056, 9) should return '-0.000559999' but returns 0.0000000
391
        // let use this $x - floor($x/$y) * $y;
392 55
        return static::formatTrailingZeroes(
393 55
            static::sub(
394 55
                $dividend,
395 55
                static::mul(static::floor(static::div($dividend, $divisor, $scale)), $divisor, $scale),
396 55
                $scale
397
            ),
398 55
            $scale
399
        );
400
    }
401
402 102
    public static function floor(string $number): string
403
    {
404 102
        $number = static::trimTrailingZeroes(static::convertScientificNotationToString($number));
405 102
        if (static::isFloat($number)) {
406 46
            $result = 0;
407 46
            if (static::isNegative($number)) {
408 9
                --$result;
409
            }
410 46
            $number = static::add($number, (string)$result, 0);
411
        }
412
413 102
        return static::parseNumber($number);
414
    }
415
416 8
    public static function fact(string $number): string
417
    {
418 8
        $number = static::convertScientificNotationToString($number);
419
420 8
        if (static::isFloat($number)) {
421 1
            throw new InvalidArgumentException('Number has to be an integer');
422
        }
423 7
        if (static::isNegative($number)) {
424 1
            throw new InvalidArgumentException('Number has to be greater than or equal to 0');
425
        }
426
427 6
        $return = '1';
428 6
        for ($i = 2; $i <= $number; ++$i) {
429 5
            $return = static::mul($return, (string)$i);
430
        }
431
432 6
        return $return;
433
    }
434
435 7
    public static function hexdec(string $hex): string
436
    {
437 7
        $remainingDigits = str_replace('0x', '', substr($hex, 0, -1));
438 7
        $lastDigitToDecimal = (string)hexdec(substr($hex, -1));
439
440 7
        if ('' === $remainingDigits) {
441 7
            return $lastDigitToDecimal;
442
        }
443
444 7
        return static::add(static::mul('16', static::hexdec($remainingDigits)), $lastDigitToDecimal, 0);
445
    }
446
447 6
    public static function dechex(string $decimal): string
448
    {
449 6
        $quotient = static::div($decimal, '16', 0);
450 6
        $remainderToHex = dechex((int)static::mod($decimal, '16'));
451
452 6
        if (static::comp($quotient, '0') === static::COMPARE_EQUAL) {
453 6
            return $remainderToHex;
454
        }
455
456 6
        return static::dechex($quotient) . $remainderToHex;
457
    }
458
459 12
    public static function bitAnd(
460
        string $leftOperand,
461
        string $rightOperand
462
    ): string {
463 12
        return static::bitOperatorHelper($leftOperand, $rightOperand, static::BIT_OPERATOR_AND);
464
    }
465
466 36
    protected static function bitOperatorHelper(string $leftOperand, string $rightOperand, string $operator): string
467
    {
468 36
        $leftOperand = static::convertScientificNotationToString($leftOperand);
469 36
        $rightOperand = static::convertScientificNotationToString($rightOperand);
470
471 36
        if (static::isFloat($leftOperand)) {
472 3
            throw new InvalidArgumentException('Left operator has to be an integer');
473
        }
474 33
        if (static::isFloat($rightOperand)) {
475 3
            throw new InvalidArgumentException('Right operator has to be an integer');
476
        }
477
478 30
        $leftOperandNegative = static::isNegative($leftOperand);
479 30
        $rightOperandNegative = static::isNegative($rightOperand);
480
481 30
        $leftOperand = static::dec2bin(static::abs($leftOperand));
482 30
        $rightOperand = static::dec2bin(static::abs($rightOperand));
483
484 30
        $maxLength = max(strlen($leftOperand), strlen($rightOperand));
485
486 30
        $leftOperand = static::alignBinLength($leftOperand, $maxLength);
487 30
        $rightOperand = static::alignBinLength($rightOperand, $maxLength);
488
489 30
        if ($leftOperandNegative) {
490 7
            $leftOperand = static::recalculateNegative($leftOperand);
491
        }
492 30
        if ($rightOperandNegative) {
493 7
            $rightOperand = static::recalculateNegative($rightOperand);
494
        }
495
496 30
        $isNegative = false;
497 30
        $result = '';
498 30
        if (static::BIT_OPERATOR_AND === $operator) {
499 10
            $result = $leftOperand & $rightOperand;
500 10
            $isNegative = ($leftOperandNegative and $rightOperandNegative);
501 20
        } elseif (static::BIT_OPERATOR_OR === $operator) {
502 12
            $result = $leftOperand | $rightOperand;
503 12
            $isNegative = ($leftOperandNegative or $rightOperandNegative);
504 8
        } elseif (static::BIT_OPERATOR_XOR === $operator) {
505 8
            $result = $leftOperand ^ $rightOperand;
506 8
            $isNegative = ($leftOperandNegative xor $rightOperandNegative);
507
        }
508
509 30
        if ($isNegative) {
510 8
            $result = static::recalculateNegative($result);
511
        }
512
513 30
        $result = static::bin2dec($result);
514
515 30
        return $isNegative ? '-' . $result : $result;
516
    }
517
518 36
    public static function dec2bin(string $number, int $base = self::MAX_BASE): string
519
    {
520 36
        return static::decBaseHelper(
521 36
            $base,
522
            static function (int $base) use ($number) {
523 35
                $value = '';
524 35
                if ('0' === $number) {
525 30
                    return chr((int)$number);
526
                }
527
528 33
                while (BC::comp($number, '0') !== BC::COMPARE_EQUAL) {
529 33
                    $rest = BC::mod($number, (string)$base);
530 33
                    $number = BC::div($number, (string)$base);
531 33
                    $value = chr((int)$rest) . $value;
532
                }
533
534 33
                return $value;
535 36
            }
536
        );
537
    }
538
539 37
    protected static function decBaseHelper(int $base, Closure $closure): string
540
    {
541 37
        if ($base < 2 || $base > static::MAX_BASE) {
542 2
            throw new InvalidArgumentException('Invalid Base: ' . $base);
543
        }
544 35
        $orgScale = static::getScale();
545 35
        static::setScale(0);
546
547 35
        $value = $closure($base);
548
549 35
        static::setScale($orgScale);
550
551 35
        return $value;
552
    }
553
554 401
    public static function setScale(int $scale): void
555
    {
556 401
        bcscale($scale);
557 401
    }
558
559 45
    public static function abs(string $number): string
560
    {
561 45
        $number = static::convertScientificNotationToString($number);
562
563 45
        if (static::isNegative($number)) {
564 19
            $number = (string)substr($number, 1);
565
        }
566
567 45
        return static::parseNumber($number);
568
    }
569
570 30
    protected static function alignBinLength(string $string, int $length): string
571
    {
572 30
        return str_pad($string, $length, static::dec2bin('0'), STR_PAD_LEFT);
573
    }
574
575 11
    protected static function recalculateNegative(string $number): string
576
    {
577 11
        $xor = str_repeat(static::dec2bin((string)(static::MAX_BASE - 1)), strlen($number));
578 11
        $number ^= $xor;
579 11
        for ($i = strlen($number) - 1; $i >= 0; --$i) {
580 11
            $byte = ord($number[$i]);
581 11
            if (++$byte !== static::MAX_BASE) {
582 11
                $number[$i] = chr($byte);
583 11
                break;
584
            }
585
        }
586
587 11
        return $number;
588
    }
589
590 36
    public static function bin2dec(string $binary, int $base = self::MAX_BASE): string
591
    {
592 36
        return static::decBaseHelper(
593 36
            $base,
594
            static function (int $base) use ($binary) {
595 35
                $size = strlen($binary);
596 35
                $return = '0';
597 35
                for ($i = 0; $i < $size; ++$i) {
598 35
                    $element = ord($binary[$i]);
599 35
                    $power = BC::pow((string)$base, (string)($size - $i - 1));
600 35
                    $return = BC::add($return, BC::mul((string)$element, $power));
601
                }
602
603 35
                return $return;
604 36
            }
605
        );
606
    }
607
608 14
    public static function bitOr(string $leftOperand, string $rightOperand): string
609
    {
610 14
        return static::bitOperatorHelper($leftOperand, $rightOperand, static::BIT_OPERATOR_OR);
611
    }
612
613 10
    public static function bitXor(string $leftOperand, string $rightOperand): string
614
    {
615 10
        return static::bitOperatorHelper($leftOperand, $rightOperand, static::BIT_OPERATOR_XOR);
616
    }
617
618 25
    public static function roundHalfEven(string $number, int $precision = 0): string
619
    {
620 25
        $number = static::convertScientificNotationToString($number);
621 25
        if (!static::isFloat($number)) {
622
            return $number;
623
        }
624
625 25
        $precessionPos = strpos($number, '.') + $precision + 1;
626 25
        if (strlen($number) <= $precessionPos) {
627 1
            return static::round($number, $precision);
628
        }
629
630 24
        if ($number[-1] !== '5') {
631 9
            return static::round($number, $precision);
632
        }
633
634 15
        $isPrevEven = $number[$precessionPos - 1] === '.'
635 4
            ? (int)$number[$precessionPos - 2] % 2 === 0
636 15
            : (int)$number[$precessionPos - 1] % 2 === 0;
637 15
        $isNegative = static::isNegative($number);
638
639 15
        if ($isPrevEven === $isNegative) {
640 7
            return static::roundUp($number, $precision);
641
        }
642
643 8
        return static::roundDown($number, $precision);
644
    }
645
646 58
    public static function round(string $number, int $precision = 0): string
647
    {
648 58
        $number = static::convertScientificNotationToString($number);
649 58
        if (static::isFloat($number)) {
650 47
            if (static::isNegative($number)) {
651 9
                $number = static::sub($number, '0.' . str_repeat('0', $precision) . '5', $precision);
652
            } else {
653 38
                $number = static::add($number, '0.' . str_repeat('0', $precision) . '5', $precision);
654
            }
655
        }
656
657 58
        return static::parseNumber($number);
658
    }
659
660 24
    public static function roundUp(string $number, int $precision = 0): string
661
    {
662 24
        $number = static::convertScientificNotationToString($number);
663 24
        if (!static::isFloat($number)) {
664 4
            return $number;
665
        }
666 20
        $multiply = static::pow('10', (string)abs($precision));
667
668 20
        return static::parseNumber(
669 20
            $precision < 0
670
                ?
671 4
                static::mul(
672 4
                    static::ceil(static::div($number, $multiply, static::getDecimalsLength($number))),
673 4
                    $multiply,
674 4
                    (int)abs($precision)
675
                )
676
                :
677 16
                static::div(
678 16
                    static::ceil(static::mul($number, $multiply, static::getDecimalsLength($number))),
679 16
                    $multiply,
680 20
                    $precision
681
                )
682
        );
683
    }
684
685 44
    public static function ceil(string $number): string
686
    {
687 44
        $number = static::trimTrailingZeroes(static::convertScientificNotationToString($number));
688 44
        if (static::isFloat($number)) {
689 29
            $result = 1;
690 29
            if (static::isNegative($number)) {
691 6
                --$result;
692
            }
693 29
            $number = static::add($number, (string)$result, 0);
694
        }
695
696 44
        return static::parseNumber($number);
697
    }
698
699 26
    public static function roundDown(string $number, int $precision = 0): string
700
    {
701 26
        $number = static::convertScientificNotationToString($number);
702 26
        if (!static::isFloat($number)) {
703 4
            return $number;
704
        }
705 22
        $multiply = static::pow('10', (string)abs($precision));
706
707 22
        return static::parseNumber(
708 22
            $precision < 0
709
                ?
710 4
                static::mul(
711 4
                    static::floor(static::div($number, $multiply, static::getDecimalsLength($number))),
712 4
                    $multiply,
713 4
                    (int)abs($precision)
714
                )
715
                :
716 18
                static::div(
717 18
                    static::floor(static::mul($number, $multiply, static::getDecimalsLength($number))),
718 18
                    $multiply,
719 22
                    $precision
720
                )
721
        );
722
    }
723
}
724