Passed
Pull Request — master (#40)
by Dorian
03:11
created

BC::parseValues()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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

330
            } elseif (static::comp(/** @scrutinizer ignore-type */ $max, $number) === static::COMPARE_RIGHT_GRATER) {
Loading history...
331 1
                $max = $number;
332
            }
333
        }
334
335 1
        return $max;
336
    }
337
338 2
    protected static function parseValues(array $values): array
339
    {
340 2
        if (is_array($values[0])) {
341 2
            $values = $values[0];
342
        }
343
344 2
        return $values;
345
    }
346
347
    /**
348
     * @param array<int, string|float|int|null>|string|float|int|null ...$values
349
     */
350 1
    public static function min(...$values): ?string
351
    {
352 1
        $min = null;
353 1
        foreach (static::parseValues($values) as $number) {
354 1
            $number = static::convertScientificNotationToString((string)$number);
355 1
            if (null === $min) {
356 1
                $min = $number;
357 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 null|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

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