Passed
Push — master ( 5cfd62...1562a1 )
by kacper
09:52
created

BC::addTrailingZeroes()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 8
c 0
b 0
f 0
nc 4
nop 2
dl 0
loc 16
ccs 9
cts 9
cp 1
crap 4
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BCMathExtended;
6
7
use Closure;
8
use InvalidArgumentException;
9
10
class BC
11
{
12
    public const COMPARE_EQUAL = 0;
13
    public const COMPARE_LEFT_GRATER = 1;
14
    public const COMPARE_RIGHT_GRATER = -1;
15
16
    protected const DEFAULT_SCALE = 100;
17
18
    protected const MAX_BASE = 256;
19
20
    protected const BIT_OPERATOR_AND = 'and';
21
    protected const BIT_OPERATOR_OR = 'or';
22
    protected const BIT_OPERATOR_XOR = 'xor';
23
24
    protected static $trimTrailingZeroes = true;
25
26 2
    public static function rand(string $min, string $max): string
27
    {
28 2
        $max = static::convertScientificNotationToString($max);
29 2
        $min = static::convertScientificNotationToString($min);
30
31 2
        $difference = static::add(static::sub($max, $min), '1');
32 2
        $randPercent = static::div((string)mt_rand(), (string)mt_getrandmax(), 8);
33
34 2
        return static::add($min, static::mul($difference, $randPercent, 8), 0);
35
    }
36
37 392
    public static function convertScientificNotationToString(string $number): string
38
    {
39
        // check if number is in scientific notation, first use stripos as is faster then preg_match
40 392
        if (false !== stripos($number, 'E') && preg_match('/(-?(\d+\.)?\d+)E([+-]?)(\d+)/i', $number, $regs)) {
41
            // calculate final scale of number
42 76
            $scale = $regs[4] + static::getDecimalsLengthFromNumber($regs[1]);
43 76
            $pow = static::pow('10', $regs[4], $scale);
44 76
            if ('-' === $regs[3]) {
45 31
                $number = static::div($regs[1], $pow, $scale);
46
            } else {
47 45
                $number = static::mul($pow, $regs[1], $scale);
48
            }
49
            // remove unnecessary 0 and dot from 0.000 is a 0
50 76
            $number = static::formatTrailingZeroes($number, $scale);
51
        }
52
53 392
        return static::checkNumber($number);
54
    }
55
56 123
    public static function getDecimalsLengthFromNumber(string $number): int
57
    {
58 123
        $check = explode('.', $number);
59 123
        if (!empty($check[1])) {
60 91
            return strlen($check[1]);
61
        }
62
63 33
        return 0;
64
    }
65
66 177
    public static function pow(string $leftOperand, string $rightOperand, ?int $scale = null): string
67
    {
68 177
        $leftOperand = static::convertScientificNotationToString($leftOperand);
69 177
        $rightOperand = static::convertScientificNotationToString($rightOperand);
70
71 177
        if (static::checkIsFloat($rightOperand)) {
72 9
            if (null === $scale) {
73 1
                $r = static::powFractional($leftOperand, $rightOperand);
74
            } else {
75 9
                $r = static::powFractional($leftOperand, $rightOperand, $scale);
76
            }
77 169
        } elseif (null === $scale) {
78 85
            $r = bcpow($leftOperand, $rightOperand);
79
        } else {
80 87
            $r = bcpow($leftOperand, $rightOperand, $scale);
81
        }
82
83 177
        return static::formatTrailingZeroes($r, $scale);
84
    }
85
86 301
    protected static function checkIsFloat(string $number): bool
87
    {
88 301
        return false !== strpos($number, '.');
89
    }
90
91 9
    protected static function powFractional(string $leftOperand, string $rightOperand, ?int $scale = null): string
92
    {
93
        // we need to increased scale to get correct results and avoid rounding error
94 9
        $currentScale = $scale ?? static::getScale();
95 9
        $increasedScale = $currentScale * 2;
96
97
        // add zero to trim scale
98 9
        return static::checkNumber(
99 9
            static::add(
100 9
                static::exp(static::mul($rightOperand, static::log($leftOperand), $increasedScale)),
101 9
                '0',
102 9
                $currentScale
103
            )
104
        );
105
    }
106
107 39
    public static function getScale(): int
108
    {
109 39
        if (PHP_VERSION_ID >= 70300) {
110
            /** @noinspection PhpStrictTypeCheckingInspection */
111
            /** @noinspection PhpParamsInspection */
112
            return bcscale();
0 ignored issues
show
Bug Best Practice introduced by
The expression return bcscale() returns the type boolean which is incompatible with the type-hinted return integer.
Loading history...
Bug introduced by
The call to bcscale() has too few arguments starting with scale. ( Ignorable by Annotation )

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

112
            return /** @scrutinizer ignore-call */ bcscale();

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
113
        }
114
115 39
        $sqrt = static::sqrt('2');
116
117 39
        return strlen(substr($sqrt, strpos($sqrt, '.') + 1));
118
    }
119
120 44
    public static function sqrt(string $operand, ?int $scale = null): string
121
    {
122 44
        $operand = static::convertScientificNotationToString($operand);
123
124 44
        if (null === $scale) {
125 40
            $r = bcsqrt($operand);
126
        } else {
127 6
            $r = bcsqrt($operand, $scale);
128
        }
129
130 44
        return static::formatTrailingZeroes($r, $scale);
131
    }
132
133 315
    protected static function formatTrailingZeroes(string $number, ?int $scale = null): string
134
    {
135 315
        if (self::$trimTrailingZeroes) {
136 315
            return static::trimTrailingZeroes($number);
137
        }
138
139
        // newer version of php correct add trailing zeros
140 1
        if (PHP_VERSION_ID >= 70300) {
141
            return $number;
142
        }
143
144
        // old one not so much..
145 1
        return self::addTrailingZeroes($number, $scale);
146
    }
147
148 318
    protected static function trimTrailingZeroes(string $number): string
149
    {
150 318
        if (false !== strpos($number, '.')) {
151 245
            $number = rtrim($number, '0');
152
        }
153
154 318
        return rtrim($number, '.') ?: '0';
155
    }
156
157 1
    protected static function addTrailingZeroes(string $number, ?int $scale): string
158
    {
159 1
        if (null === $scale) {
160 1
            return $number;
161
        }
162
163 1
        $decimalLength = static::getDecimalsLengthFromNumber($number);
164 1
        if ($scale === $decimalLength) {
165 1
            return $number;
166
        }
167
168 1
        if (0 === $decimalLength) {
169 1
            $number .= '.';
170
        }
171
172 1
        return str_pad($number, strlen($number) + ($scale - $decimalLength), '0', STR_PAD_RIGHT);
173
    }
174
175 392
    protected static function checkNumber(string $number): string
176
    {
177 392
        $number = str_replace('+', '', filter_var($number, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION));
178 392
        if ('-0' === $number || !is_numeric($number)) {
179 30
            return '0';
180
        }
181
182 389
        return $number;
183
    }
184
185 179
    public static function add(string $leftOperand, string $rightOperand, ?int $scale = null): string
186
    {
187 179
        $leftOperand = static::convertScientificNotationToString($leftOperand);
188 179
        $rightOperand = static::convertScientificNotationToString($rightOperand);
189
190 179
        if (null === $scale) {
191 41
            $r = bcadd($leftOperand, $rightOperand);
192
        } else {
193 141
            $r = bcadd($leftOperand, $rightOperand, $scale);
194
        }
195
196 179
        return static::formatTrailingZeroes($r, $scale);
197
    }
198
199 17
    public static function exp(string $arg): string
200
    {
201 17
        $scale = static::DEFAULT_SCALE;
202 17
        $result = '1';
203 17
        for ($i = 299; $i > 0; --$i) {
204 17
            $result = static::add(static::mul(static::div($result, (string)$i, $scale), $arg, $scale), '1', $scale);
205
        }
206
207 17
        return $result;
208
    }
209
210 189
    public static function mul(string $leftOperand, string $rightOperand, ?int $scale = null): string
211
    {
212 189
        $leftOperand = static::convertScientificNotationToString($leftOperand);
213 189
        $rightOperand = static::convertScientificNotationToString($rightOperand);
214
215 189
        if (null === $scale) {
216 52
            $r = bcmul($leftOperand, $rightOperand);
217
        } else {
218 139
            $r = bcmul($leftOperand, $rightOperand, $scale);
219
        }
220
221 189
        return static::formatTrailingZeroes($r, $scale);
222
    }
223
224 152
    public static function div(string $leftOperand, string $rightOperand, ?int $scale = null): string
225
    {
226 152
        $leftOperand = static::convertScientificNotationToString($leftOperand);
227 152
        $rightOperand = static::convertScientificNotationToString($rightOperand);
228
229 152
        if ('0' === static::trimTrailingZeroes($rightOperand)) {
230 1
            throw new InvalidArgumentException('Division by zero');
231
        }
232
233 151
        if (null === $scale) {
234 41
            $r = bcdiv($leftOperand, $rightOperand);
235
        } else {
236 118
            $r = bcdiv($leftOperand, $rightOperand, $scale);
237
        }
238
239 151
        return static::formatTrailingZeroes($r, $scale);
240
    }
241
242 14
    public static function log(string $arg): string
243
    {
244 14
        $arg = static::convertScientificNotationToString($arg);
245 14
        if ($arg === '0') {
246 1
            return '-INF';
247
        }
248 13
        if (static::COMPARE_RIGHT_GRATER === static::comp($arg, '0')) {
249 1
            return 'NAN';
250
        }
251 12
        $scale = static::DEFAULT_SCALE;
252 12
        $m = (string)log((float)$arg);
253 12
        $x = static::sub(static::div($arg, static::exp($m), $scale), '1', $scale);
254 12
        $res = '0';
255 12
        $pow = '1';
256 12
        $i = 1;
257
        do {
258 12
            $pow = static::mul($pow, $x, $scale);
259 12
            $sum = static::div($pow, (string)$i, $scale);
260 12
            if ($i % 2 === 1) {
261 12
                $res = static::add($res, $sum, $scale);
262
            } else {
263 11
                $res = static::sub($res, $sum, $scale);
264
            }
265 12
            ++$i;
266 12
        } while (static::comp($sum, '0', $scale));
267
268 12
        return static::add($res, $m, $scale);
269
    }
270
271 66
    public static function comp(string $leftOperand, string $rightOperand, ?int $scale = null): int
272
    {
273 66
        $leftOperand = static::convertScientificNotationToString($leftOperand);
274 66
        $rightOperand = static::convertScientificNotationToString($rightOperand);
275
276 66
        if (null === $scale) {
277 54
            return bccomp($leftOperand, $rightOperand, max(strlen($leftOperand), strlen($rightOperand)));
278
        }
279
280 24
        return bccomp(
281 24
            $leftOperand,
282 24
            $rightOperand,
283 24
            $scale
284
        );
285
    }
286
287 83
    public static function sub(string $leftOperand, string $rightOperand, ?int $scale = null): string
288
    {
289 83
        $leftOperand = static::convertScientificNotationToString($leftOperand);
290 83
        $rightOperand = static::convertScientificNotationToString($rightOperand);
291
292 83
        if (null === $scale) {
293 43
            $r = bcsub($leftOperand, $rightOperand);
294
        } else {
295 42
            $r = bcsub($leftOperand, $rightOperand, $scale);
296
        }
297
298 83
        return static::formatTrailingZeroes($r, $scale);
299
    }
300
301 1
    public static function setTrimTrailingZeroes(bool $flag): void
302
    {
303 1
        self::$trimTrailingZeroes = $flag;
304 1
    }
305
306 1
    public static function max(...$ags): ?string
307
    {
308 1
        $max = null;
309 1
        foreach (static::parseArgs($ags) as $number) {
310 1
            $number = static::convertScientificNotationToString((string)$number);
311 1
            if (null === $max) {
312 1
                $max = $number;
313 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

313
            } elseif (static::comp(/** @scrutinizer ignore-type */ $max, $number) === static::COMPARE_RIGHT_GRATER) {
Loading history...
314 1
                $max = $number;
315
            }
316
        }
317
318 1
        return $max;
319
    }
320
321 2
    protected static function parseArgs(array $args): array
322
    {
323 2
        if (is_array($args[0])) {
324 2
            $args = $args[0];
325
        }
326
327 2
        return $args;
328
    }
329
330 1
    public static function min(...$ags): ?string
331
    {
332 1
        $min = null;
333 1
        foreach (static::parseArgs($ags) as $number) {
334 1
            $number = static::convertScientificNotationToString((string)$number);
335 1
            if (null === $min) {
336 1
                $min = $number;
337 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

337
            } elseif (static::comp(/** @scrutinizer ignore-type */ $min, $number) === static::COMPARE_LEFT_GRATER) {
Loading history...
338 1
                $min = $number;
339
            }
340
        }
341
342 1
        return $min;
343
    }
344
345 11
    public static function powMod(
346
        string $leftOperand,
347
        string $rightOperand,
348
        string $modulus,
349
        ?int $scale = null
350
    ): string {
351 11
        $leftOperand = static::convertScientificNotationToString($leftOperand);
352 11
        $rightOperand = static::convertScientificNotationToString($rightOperand);
353
354
        // bcpowmod in 5.6 have don't calculate correct results if scale is empty
355 11
        if (null === $scale) {
356 1
            $r = static::mod(static::pow($leftOperand, $rightOperand), $modulus);
357 10
        } elseif (static::checkIsFloat($leftOperand) || static::checkIsFloat($rightOperand) || static::checkIsFloat(
358 10
                $modulus
359
            )) {
360
            // cant use bcpowmod here as it don't support floats
361 4
            $r = static::mod(static::pow($leftOperand, $rightOperand, $scale), $modulus, $scale);
362
        } else {
363 6
            $r = bcpowmod($leftOperand, $rightOperand, $modulus, $scale);
364
        }
365
366 11
        return static::formatTrailingZeroes($r, $scale);
0 ignored issues
show
Bug introduced by
It seems like $r can also be of type null; however, parameter $number of BCMathExtended\BC::formatTrailingZeroes() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

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