Passed
Push — master ( 1562a1...8aab3d )
by kacper
02:23
created

BC::powMod()   B

Complexity

Conditions 8
Paths 8

Size

Total Lines 35
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 8.1039

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 8
eloc 18
c 1
b 0
f 1
nc 8
nop 4
dl 0
loc 35
ccs 15
cts 17
cp 0.8824
crap 8.1039
rs 8.4444
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 395
    public static function convertScientificNotationToString(string $number): string
39
    {
40
        // check if number is in scientific notation, first use stripos as is faster then preg_match
41 395
        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 395
        return static::parseNumber($number);
54
    }
55
56 123
    public static function getDecimalsLength(string $number): int
57
    {
58 123
        if (static::isFloat($number)) {
59 105
            return strcspn(strrev($number), '.');
60
        }
61
62 19
        return 0;
63
    }
64
65 359
    protected static function isFloat(string $number): bool
66
    {
67 359
        return false !== strpos($number, '.');
68
    }
69
70 177
    public static function pow(string $base, string $exponent, ?int $scale = null): string
71
    {
72 177
        $base = static::convertScientificNotationToString($base);
73 177
        $exponent = static::convertScientificNotationToString($exponent);
74
75 177
        if (static::isFloat($exponent)) {
76 9
            $r = static::powFractional($base, $exponent, $scale);
77 169
        } elseif (null === $scale) {
78 85
            $r = bcpow($base, $exponent);
79
        } else {
80 87
            $r = bcpow($base, $exponent, $scale);
81
        }
82
83 177
        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();
0 ignored issues
show
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

107
            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...
Bug Best Practice introduced by
The expression return bcscale() returns the type boolean which is incompatible with the type-hinted return integer.
Loading history...
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 316
    protected static function formatTrailingZeroes(string $number, ?int $scale = null): string
129
    {
130 316
        if (self::$trimTrailingZeroes) {
131 316
            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 341
    protected static function trimTrailingZeroes(string $number): string
144
    {
145 341
        if (static::isFloat($number)) {
146 247
            $number = rtrim($number, '0');
147
        }
148
149 341
        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 395
    protected static function parseNumber(string $number): string
171
    {
172 395
        $number = str_replace('+', '', filter_var($number, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION));
173 395
        if ('-0' === $number || !is_numeric($number)) {
174 30
            return '0';
175
        }
176
177 392
        return $number;
178
    }
179
180 179
    public static function add(string $leftOperand, string $rightOperand, ?int $scale = null): string
181
    {
182 179
        $leftOperand = static::convertScientificNotationToString($leftOperand);
183 179
        $rightOperand = static::convertScientificNotationToString($rightOperand);
184
185 179
        if (null === $scale) {
186 41
            $r = bcadd($leftOperand, $rightOperand);
187
        } else {
188 141
            $r = bcadd($leftOperand, $rightOperand, $scale);
189
        }
190
191 179
        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 189
    public static function mul(string $leftOperand, string $rightOperand, ?int $scale = null): string
206
    {
207 189
        $leftOperand = static::convertScientificNotationToString($leftOperand);
208 189
        $rightOperand = static::convertScientificNotationToString($rightOperand);
209
210 189
        if (null === $scale) {
211 53
            $r = bcmul($leftOperand, $rightOperand);
212
        } else {
213 139
            $r = bcmul($leftOperand, $rightOperand, $scale);
214
        }
215
216 189
        return static::formatTrailingZeroes($r, $scale);
217
    }
218
219 152
    public static function div(string $dividend, string $divisor, ?int $scale = null): string
220
    {
221 152
        $dividend = static::convertScientificNotationToString($dividend);
222 152
        $divisor = static::convertScientificNotationToString($divisor);
223
224 152
        if ('0' === static::trimTrailingZeroes($divisor)) {
225 1
            throw new InvalidArgumentException('Division by zero');
226
        }
227
228 151
        if (null === $scale) {
229 42
            $r = bcdiv($dividend, $divisor);
230
        } else {
231 118
            $r = bcdiv($dividend, $divisor, $scale);
232
        }
233
234 151
        if (null === $r) {
235
            throw new UnexpectedValueException('bcdiv should not return null!');
236
        }
237
238 151
        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, $scale);
379
    }
380
381 180
    protected static function isNegative(string $number): bool
382
    {
383 180
        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 105
    public static function floor(string $number): string
403
    {
404 105
        $number = static::trimTrailingZeroes(static::convertScientificNotationToString($number));
405 105
        if (static::isFloat($number)) {
406 45
            $result = 0;
407 45
            if (static::isNegative($number)) {
408 9
                --$result;
409
            }
410 45
            $number = static::add($number, (string)$result, 0);
411
        }
412
413 105
        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 5
    public static function hexdec(string $hex): string
436
    {
437 5
        $remainingDigits = substr($hex, 0, -1);
438 5
        $lastDigitToDecimal = (string)hexdec(substr($hex, -1));
439
440 5
        if ('' === $remainingDigits) {
441 5
            return $lastDigitToDecimal;
442
        }
443
444 5
        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 397
    public static function setScale(int $scale): void
555
    {
556 397
        bcscale($scale);
557 397
    }
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 24
    public static function roundHalfEven(string $number, int $precision = 0): string
619
    {
620 24
        $number = static::convertScientificNotationToString($number);
621 24
        if (!static::isFloat($number)) {
622
            return static::parseNumber($number);
623
        }
624
625 24
        $precessionPos = strpos($number, '.') + $precision + 1;
626 24
        if (strlen($number) <= $precessionPos) {
627 1
            return static::round($number, $precision);
628
        }
629
630 23
        if ($number[$precessionPos] !== '5') {
631 8
            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 57
    public static function round(string $number, int $precision = 0): string
647
    {
648 57
        $number = static::convertScientificNotationToString($number);
649 57
        if (static::isFloat($number)) {
650 46
            if (static::isNegative($number)) {
651 9
                return static::sub($number, '0.' . str_repeat('0', $precision) . '5', $precision);
652
            }
653
654 37
            return static::add($number, '0.' . str_repeat('0', $precision) . '5', $precision);
655
        }
656
657 11
        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
        $multiply = static::pow('10', (string)abs($precision));
664
665 24
        return $precision < 0
666
            ?
667 4
            static::mul(
668 4
                static::ceil(static::div($number, $multiply, static::getDecimalsLength($number))),
669 4
                $multiply,
670 4
                $precision
671
            )
672
            :
673 20
            static::div(
674 20
                static::ceil(static::mul($number, $multiply, static::getDecimalsLength($number))),
675 20
                $multiply,
676 24
                $precision
677
            );
678
    }
679
680 48
    public static function ceil(string $number): string
681
    {
682 48
        $number = static::trimTrailingZeroes(static::convertScientificNotationToString($number));
683 48
        if (static::isFloat($number)) {
684 29
            $result = 1;
685 29
            if (static::isNegative($number)) {
686 6
                --$result;
687
            }
688 29
            $number = static::add($number, (string)$result, 0);
689
        }
690
691 48
        return static::parseNumber($number);
692
    }
693
694 25
    public static function roundDown(string $number, int $precision = 0): string
695
    {
696 25
        $number = static::convertScientificNotationToString($number);
697 25
        $multiply = static::pow('10', (string)abs($precision));
698
699 25
        return $precision < 0
700
            ?
701 4
            static::mul(
702 4
                static::floor(static::div($number, $multiply, static::getDecimalsLength($number))),
703 4
                $multiply,
704 4
                $precision
705
            )
706
            :
707 21
            static::div(
708 21
                static::floor(static::mul($number, $multiply, static::getDecimalsLength($number))),
709 21
                $multiply,
710 25
                $precision
711
            );
712
    }
713
}
714