Passed
Push — master ( 4379ac...bba0ab )
by kacper
02:17
created

BC::roundHalfEven()   A

Complexity

Conditions 6
Paths 7

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 6.0087

Importance

Changes 0
Metric Value
cc 6
eloc 15
c 0
b 0
f 0
nc 7
nop 2
dl 0
loc 27
ccs 15
cts 16
cp 0.9375
crap 6.0087
rs 9.2222
1
<?php
2
declare(strict_types=1);
3
4
namespace BCMathExtended;
5
6
use Closure;
7
use InvalidArgumentException;
8
use function dechex;
9
use function hexdec;
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 56
    public static function round(string $number, int $precision = 0): string
26
    {
27 56
        $number = self::convertScientificNotationToString($number);
28 56
        if (self::checkIsFloat($number)) {
29 45
            if (self::isNegative($number)) {
30 9
                return self::sub($number, '0.' . str_repeat('0', $precision) . '5', $precision);
31
            }
32
33 36
            return self::add($number, '0.' . str_repeat('0', $precision) . '5', $precision);
34
        }
35
36 11
        return self::checkNumber($number);
37
    }
38
39 391
    public static function convertScientificNotationToString(string $number): string
40
    {
41
        // check if number is in scientific notation, first use stripos as is faster then preg_match
42 391
        if (false !== stripos($number, 'E') && preg_match('/(-?(\d+\.)?\d+)E([+-]?)(\d+)/i', $number, $regs)) {
43
            // calculate final scale of number
44 60
            $scale = $regs[4] + self::getDecimalsLengthFromNumber($regs[1]);
45 60
            $pow = self::pow('10', $regs[4], $scale);
46 60
            if ('-' === $regs[3]) {
47 25
                $number = self::div($regs[1], $pow, $scale);
48
            } else {
49 35
                $number = self::mul($pow, $regs[1], $scale);
50
            }
51
            // remove unnecessary 0 and dot from 0.000 is a 0
52 60
            $number = self::trimTrailingZeroes($number);
53
        }
54
55 391
        return self::checkNumber($number);
56
    }
57
58 107
    public static function getDecimalsLengthFromNumber(string $number): int
59
    {
60 107
        $check = explode('.', $number);
61 107
        if (!empty($check[1])) {
62 77
            return strlen($check[1]);
63
        }
64
65 30
        return 0;
66
    }
67
68 161
    public static function pow(string $leftOperand, string $rightOperand, ?int $scale = null): string
69
    {
70 161
        $leftOperand = self::convertScientificNotationToString($leftOperand);
71 161
        $rightOperand = self::convertScientificNotationToString($rightOperand);
72
73 161
        if (self::checkIsFloat($rightOperand)) {
74 8
            if (null === $scale) {
75
                $r = self::powFractional($leftOperand, $rightOperand);
76
            } else {
77 8
                $r = self::powFractional($leftOperand, $rightOperand, $scale);
78
            }
79 153
        } else if (null === $scale) {
80 85
            $r = bcpow($leftOperand, $rightOperand);
81
        } else {
82 71
            $r = bcpow($leftOperand, $rightOperand, $scale);
83
        }
84
85 161
        return self::trimTrailingZeroes($r);
86
    }
87
88 288
    protected static function checkIsFloat(string $number): bool
89
    {
90 288
        return false !== strpos($number, '.');
91
    }
92
93 8
    protected static function powFractional(string $leftOperand, string $rightOperand, ?int $scale = null): string
94
    {
95
        // we need to increased scale to get correct results and avoid rounding error
96 8
        $currentScale = $scale ?? self::getScale();
97 8
        $increasedScale = $currentScale * 2;
98
99
        // add zero to trim scale
100 8
        return self::checkNumber(self::add(self::exp(self::mul($rightOperand, self::log($leftOperand), $increasedScale)), '0', $currentScale));
101
    }
102
103 38
    public static function getScale(): int
104
    {
105 38
        if (PHP_VERSION_ID >= 70300) {
106
            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

106
            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...
107
        }
108
109 38
        $sqrt = self::sqrt('2');
110
111 38
        return strlen(substr($sqrt, strpos($sqrt, '.') + 1));
112
    }
113
114 43
    public static function sqrt(string $operand, ?int $scale = null): string
115
    {
116 43
        $operand = self::convertScientificNotationToString($operand);
117
118 43
        if (null === $scale) {
119 39
            $r = bcsqrt($operand);
120
        } else {
121 5
            $r = bcsqrt($operand, $scale);
122
        }
123
124 43
        return self::trimTrailingZeroes($r);
125
    }
126
127 308
    protected static function trimTrailingZeroes(string $number): string
128
    {
129 308
        if (false !== strpos($number, '.')) {
130 234
            $number = rtrim($number, '0');
131
        }
132
133 308
        return rtrim($number, '.') ?: '0';
134
    }
135
136 391
    protected static function checkNumber(string $number): string
137
    {
138 391
        $number = str_replace('+', '', filter_var($number, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION));
139 391
        if ('-0' === $number || !is_numeric($number)) {
140 28
            return '0';
141
        }
142
143 388
        return $number;
144
    }
145
146 181
    public static function mul(string $leftOperand, string $rightOperand, ?int $scale = null): string
147
    {
148 181
        $leftOperand = self::convertScientificNotationToString($leftOperand);
149 181
        $rightOperand = self::convertScientificNotationToString($rightOperand);
150
151 181
        if (null === $scale) {
152 51
            $r = bcmul($leftOperand, $rightOperand);
153
        } else {
154 130
            $r = bcmul($leftOperand, $rightOperand, $scale);
155
        }
156
157 181
        return self::trimTrailingZeroes($r);
158
    }
159
160 16
    public static function exp(string $arg): string
161
    {
162 16
        $scale = self::DEFAULT_SCALE;
163 16
        $result = '1';
164 16
        for ($i = 299; $i > 0; $i--) {
165 16
            $result = self::add(self::mul(self::div($result, (string)$i, $scale), $arg, $scale), '1', $scale);
166
        }
167
168 16
        return $result;
169
    }
170
171 178
    public static function add(string $leftOperand, string $rightOperand, ?int $scale = null): string
172
    {
173 178
        $leftOperand = self::convertScientificNotationToString($leftOperand);
174 178
        $rightOperand = self::convertScientificNotationToString($rightOperand);
175
176 178
        if (null === $scale) {
177 41
            $r = bcadd($leftOperand, $rightOperand);
178
        } else {
179 140
            $r = bcadd($leftOperand, $rightOperand, $scale);
180
        }
181
182 178
        return self::trimTrailingZeroes($r);
183
    }
184
185 145
    public static function div(string $leftOperand, string $rightOperand, ?int $scale = null): string
186
    {
187 145
        $leftOperand = self::convertScientificNotationToString($leftOperand);
188 145
        $rightOperand = self::convertScientificNotationToString($rightOperand);
189
190 145
        if ('0' === self::trimTrailingZeroes($rightOperand)) {
191 1
            throw new InvalidArgumentException('Division by zero');
192
        }
193
194 144
        if (null === $scale) {
195 40
            $r = bcdiv($leftOperand, $rightOperand);
196
        } else {
197 111
            $r = bcdiv($leftOperand, $rightOperand, $scale);
198
        }
199
200 144
        return self::trimTrailingZeroes($r);
201
    }
202
203 13
    public static function log(string $arg): string
204
    {
205 13
        $arg = self::convertScientificNotationToString($arg);
206 13
        if ($arg === '0') {
207 1
            return '-INF';
208
        }
209 12
        if (self::COMPARE_RIGHT_GRATER === self::comp($arg, '0')) {
210 1
            return 'NAN';
211
        }
212 11
        $scale = self::DEFAULT_SCALE;
213 11
        $m = (string)log((float)$arg);
214 11
        $x = self::sub(self::div($arg, self::exp($m), $scale), '1', $scale);
215 11
        $res = '0';
216 11
        $pow = '1';
217 11
        $i = 1;
218
        do {
219 11
            $pow = self::mul($pow, $x, $scale);
220 11
            $sum = self::div($pow, (string)$i, $scale);
221 11
            if ($i % 2 === 1) {
222 11
                $res = self::add($res, $sum, $scale);
223
            } else {
224 10
                $res = self::sub($res, $sum, $scale);
225
            }
226 11
            $i++;
227 11
        } while (self::comp($sum, '0', $scale));
228
229 11
        return self::add($res, $m, $scale);
230
    }
231
232 65
    public static function comp(string $leftOperand, string $rightOperand, ?int $scale = null): int
233
    {
234 65
        $leftOperand = self::convertScientificNotationToString($leftOperand);
235 65
        $rightOperand = self::convertScientificNotationToString($rightOperand);
236
237 65
        if (null === $scale) {
238 53
            return bccomp($leftOperand, $rightOperand, max(strlen($leftOperand), strlen($rightOperand)));
239
        }
240
241 23
        return bccomp(
242 23
            $leftOperand,
243 23
            $rightOperand,
244 23
            $scale
245
        );
246
    }
247
248 82
    public static function sub(string $leftOperand, string $rightOperand, ?int $scale = null): string
249
    {
250 82
        $leftOperand = self::convertScientificNotationToString($leftOperand);
251 82
        $rightOperand = self::convertScientificNotationToString($rightOperand);
252
253 82
        if (null === $scale) {
254 42
            $r = bcsub($leftOperand, $rightOperand);
255
        } else {
256 41
            $r = bcsub($leftOperand, $rightOperand, $scale);
257
        }
258
259 82
        return self::trimTrailingZeroes($r);
260
    }
261
262 170
    protected static function isNegative(string $number): bool
263
    {
264 170
        return 0 === strncmp('-', $number, 1);
265
    }
266
267 2
    public static function rand(string $min, string $max): string
268
    {
269 2
        $max = self::convertScientificNotationToString($max);
270 2
        $min = self::convertScientificNotationToString($min);
271
272 2
        $difference = self::add(self::sub($max, $min), '1');
273 2
        $randPercent = self::div((string)mt_rand(), (string)mt_getrandmax(), 8);
274
275 2
        return self::add($min, self::mul($difference, $randPercent, 8), 0);
276
    }
277
278 1
    public static function max(...$ags): ?string
279
    {
280 1
        $max = null;
281 1
        foreach (self::parseArgs($ags) as $number) {
282 1
            $number = self::convertScientificNotationToString((string)$number);
283 1
            if (null === $max) {
284 1
                $max = $number;
285 1
            } elseif (self::comp((string)$max, $number) === self::COMPARE_RIGHT_GRATER) {
286 1
                $max = $number;
287
            }
288
        }
289
290 1
        return $max;
291
    }
292
293 2
    protected static function parseArgs(array $args): array
294
    {
295 2
        if (is_array($args[0])) {
296 2
            $args = $args[0];
297
        }
298
299 2
        return $args;
300
    }
301
302 1
    public static function min(...$ags): ?string
303
    {
304 1
        $min = null;
305 1
        foreach (self::parseArgs($ags) as $number) {
306 1
            $number = self::convertScientificNotationToString((string)$number);
307 1
            if (null === $min) {
308 1
                $min = $number;
309 1
            } elseif (self::comp((string)$min, $number) === self::COMPARE_LEFT_GRATER) {
310 1
                $min = $number;
311
            }
312
        }
313
314 1
        return $min;
315
    }
316
317 25
    public static function roundDown(string $number, int $precision = 0): string
318
    {
319 25
        $number = self::convertScientificNotationToString($number);
320 25
        $multiply = self::pow('10', (string)abs($precision));
321
322 25
        return $precision < 0
323
            ?
324 4
            self::mul(
325 4
                self::floor(self::div($number, $multiply, self::getDecimalsLengthFromNumber($number))), $multiply,
326 4
                $precision
327
            )
328
            :
329 21
            self::div(
330 21
                self::floor(self::mul($number, $multiply, self::getDecimalsLengthFromNumber($number))), $multiply,
331 25
                $precision
332
            );
333
    }
334
335 104
    public static function floor(string $number): string
336
    {
337 104
        $number = self::convertScientificNotationToString($number);
338 104
        if (self::checkIsFloat($number) && self::checkIsFloatCleanZeros($number)) {
339 44
            $result = 0;
340 44
            if (self::isNegative($number)) {
341 9
                --$result;
342
            }
343 44
            $number = self::add($number, (string)$result, 0);
344
        }
345
346 104
        return self::checkNumber($number);
347
    }
348
349 75
    protected static function checkIsFloatCleanZeros(string &$number): bool
350
    {
351 75
        return false !== strpos($number = self::trimTrailingZeroes($number), '.');
352
    }
353
354 24
    public static function roundUp(string $number, int $precision = 0): string
355
    {
356 24
        $number = self::convertScientificNotationToString($number);
357 24
        $multiply = self::pow('10', (string)abs($precision));
358
359 24
        return $precision < 0
360
            ?
361 4
            self::mul(
362 4
                self::ceil(self::div($number, $multiply, self::getDecimalsLengthFromNumber($number))), $multiply,
363 4
                $precision
364
            )
365
            :
366 20
            self::div(
367 20
                self::ceil(self::mul($number, $multiply, self::getDecimalsLengthFromNumber($number))), $multiply,
368 24
                $precision
369
            );
370
    }
371
372 48
    public static function ceil(string $number): string
373
    {
374 48
        $number = self::convertScientificNotationToString($number);
375 48
        if (self::checkIsFloat($number) && self::checkIsFloatCleanZeros($number)) {
376 29
            $result = 1;
377 29
            if (self::isNegative($number)) {
378 6
                --$result;
379
            }
380 29
            $number = self::add($number, (string)$result, 0);
381
        }
382
383 48
        return self::checkNumber($number);
384
    }
385
386 10
    public static function powMod(
387
        string $leftOperand,
388
        string $rightOperand,
389
        string $modulus,
390
        ?int $scale = null
391
    ): string {
392 10
        $leftOperand = self::convertScientificNotationToString($leftOperand);
393 10
        $rightOperand = self::convertScientificNotationToString($rightOperand);
394
395
        // bcpowmod in 5.6 have don't calculate correct results if scale is empty
396 10
        if (null === $scale) {
397
            $r = self::mod(self::pow($leftOperand, $rightOperand), $modulus);
398 10
        } else if (self::checkIsFloat($leftOperand) || self::checkIsFloat($rightOperand) || self::checkIsFloat(
399 10
                $modulus
400
            )) {
401
            // cant use bcpowmod here as it don't support floats
402 4
            $r = self::mod(self::pow($leftOperand, $rightOperand, $scale), $modulus, $scale);
403
        } else {
404 6
            $r = bcpowmod($leftOperand, $rightOperand, $modulus, $scale);
405
        }
406
407 10
        return self::trimTrailingZeroes($r);
0 ignored issues
show
Bug introduced by
It seems like $r can also be of type null; however, parameter $number of BCMathExtended\BC::trimTrailingZeroes() 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

407
        return self::trimTrailingZeroes(/** @scrutinizer ignore-type */ $r);
Loading history...
408
    }
409
410 54
    public static function mod(string $leftOperand, string $modulus, ?int $scale = null): string
411
    {
412 54
        $leftOperand = self::convertScientificNotationToString($leftOperand);
413
414
        // 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
415
416
        // bcmod in php 5.6< don't support scale and floats
417
        // let use this $x - floor($x/$y) * $y;
418 54
        if (null === $scale) {
419 39
            $r = self::sub(
420 39
                $leftOperand,
421 39
                self::mul(self::floor(self::div($leftOperand, $modulus)), $modulus)
422
            );
423
        } else {
424 15
            $r = self::sub(
425 15
                $leftOperand,
426 15
                self::mul(self::floor(self::div($leftOperand, $modulus, $scale)), $modulus, $scale),
427 15
                $scale
428
            );
429
        }
430
431 54
        return self::trimTrailingZeroes($r);
432
    }
433
434 8
    public static function fact(string $arg): string
435
    {
436 8
        $arg = self::convertScientificNotationToString($arg);
437
438 8
        if (self::checkIsFloat($arg)) {
439 1
            throw new InvalidArgumentException('Number has to be an integer');
440
        }
441 7
        if (self::isNegative($arg)) {
442 1
            throw new InvalidArgumentException('Number has to be greater than or equal to 0');
443
        }
444
445 6
        $return = '1';
446 6
        for ($i = 2; $i <= $arg; ++$i) {
447 5
            $return = self::mul($return, (string)$i);
448
        }
449
450 6
        return $return;
451
    }
452
453 5
    public static function hexdec(string $hex): string
454
    {
455 5
        $remainingDigits = substr($hex, 0, -1);
456 5
        $lastDigitToDecimal = (string)hexdec(substr($hex, -1));
457
458 5
        if ('' === $remainingDigits) {
459 5
            return $lastDigitToDecimal;
460
        }
461
462 5
        return self::add(self::mul('16', self::hexdec($remainingDigits)), $lastDigitToDecimal, 0);
463
    }
464
465 6
    public static function dechex(string $decimal): string
466
    {
467 6
        $quotient = self::div($decimal, '16', 0);
468 6
        $remainderToHex = dechex((int)self::mod($decimal, '16'));
469
470 6
        if (self::comp($quotient, '0') === self::COMPARE_EQUAL) {
471 6
            return $remainderToHex;
472
        }
473
474 6
        return self::dechex($quotient) . $remainderToHex;
475
    }
476
477 12
    public static function bitAnd(
478
        string $leftOperand,
479
        string $rightOperand
480
    ): string {
481 12
        return self::bitOperatorHelper($leftOperand, $rightOperand, self::BIT_OPERATOR_AND);
482
    }
483
484 36
    protected static function bitOperatorHelper(string $leftOperand, string $rightOperand, string $operator): string
485
    {
486 36
        $leftOperand = self::convertScientificNotationToString($leftOperand);
487 36
        $rightOperand = self::convertScientificNotationToString($rightOperand);
488
489 36
        if (self::checkIsFloat($leftOperand)) {
490 3
            throw new InvalidArgumentException('Left operator has to be an integer');
491
        }
492 33
        if (self::checkIsFloat($rightOperand)) {
493 3
            throw new InvalidArgumentException('Right operator has to be an integer');
494
        }
495
496 30
        $leftOperandNegative = self::isNegative($leftOperand);
497 30
        $rightOperandNegative = self::isNegative($rightOperand);
498
499 30
        $leftOperand = self::dec2bin(self::abs($leftOperand));
500 30
        $rightOperand = self::dec2bin(self::abs($rightOperand));
501
502 30
        $maxLength = max(strlen($leftOperand), strlen($rightOperand));
503
504 30
        $leftOperand = self::alignBinLength($leftOperand, $maxLength);
505 30
        $rightOperand = self::alignBinLength($rightOperand, $maxLength);
506
507 30
        if ($leftOperandNegative) {
508 7
            $leftOperand = self::recalculateNegative($leftOperand);
509
        }
510 30
        if ($rightOperandNegative) {
511 7
            $rightOperand = self::recalculateNegative($rightOperand);
512
        }
513
514 30
        $isNegative = false;
515 30
        $result = '';
516 30
        if (self::BIT_OPERATOR_AND === $operator) {
517 10
            $result = $leftOperand & $rightOperand;
518 10
            $isNegative = ($leftOperandNegative and $rightOperandNegative);
519 20
        } elseif (self::BIT_OPERATOR_OR === $operator) {
520 12
            $result = $leftOperand | $rightOperand;
521 12
            $isNegative = ($leftOperandNegative or $rightOperandNegative);
522 8
        } elseif (self::BIT_OPERATOR_XOR === $operator) {
523 8
            $result = $leftOperand ^ $rightOperand;
524 8
            $isNegative = ($leftOperandNegative xor $rightOperandNegative);
525
        }
526
527 30
        if ($isNegative) {
528 8
            $result = self::recalculateNegative($result);
529
        }
530
531 30
        $result = self::bin2dec($result);
532
533 30
        return $isNegative ? '-' . $result : $result;
534
    }
535
536 36
    public static function dec2bin(string $number, int $base = self::MAX_BASE): string
537
    {
538 36
        return self::decBaseHelper(
539
            $base, static function (int $base) use ($number) {
540 35
            $value = '';
541 35
            if ('0' === $number) {
542 30
                return chr((int)$number);
543
            }
544
545 33
            while (BC::comp($number, '0') !== BC::COMPARE_EQUAL) {
546 33
                $rest = BC::mod($number, (string)$base);
547 33
                $number = BC::div($number, (string)$base);
548 33
                $value = chr((int)$rest) . $value;
549
            }
550
551 33
            return $value;
552 36
        }
553
        );
554
    }
555
556 37
    protected static function decBaseHelper(int $base, Closure $closure): string
557
    {
558 37
        if ($base < 2 || $base > self::MAX_BASE) {
559 2
            throw new InvalidArgumentException('Invalid Base: ' . $base);
560
        }
561 35
        $orgScale = self::getScale();
562 35
        self::setScale(0);
563
564 35
        $value = $closure($base);
565
566 35
        self::setScale($orgScale);
567
568 35
        return $value;
569
    }
570
571 393
    public static function setScale(int $scale): void
572
    {
573 393
        bcscale($scale);
574 393
    }
575
576 45
    public static function abs(string $number): string
577
    {
578 45
        $number = self::convertScientificNotationToString($number);
579
580 45
        if (self::isNegative($number)) {
581 19
            $number = (string)substr($number, 1);
582
        }
583
584 45
        return self::checkNumber($number);
585
    }
586
587 30
    protected static function alignBinLength(string $string, int $length): string
588
    {
589 30
        return str_pad($string, $length, self::dec2bin('0'), STR_PAD_LEFT);
590
    }
591
592 11
    protected static function recalculateNegative(string $number): string
593
    {
594 11
        $xor = str_repeat(self::dec2bin((string)(self::MAX_BASE - 1)), strlen($number));
595 11
        $number ^= $xor;
596 11
        for ($i = strlen($number) - 1; $i >= 0; $i--) {
597 11
            $byte = ord($number[$i]);
598 11
            if (++$byte !== self::MAX_BASE) {
599 11
                $number[$i] = chr($byte);
600 11
                break;
601
            }
602
        }
603
604 11
        return $number;
605
    }
606
607 36
    public static function bin2dec(string $binary, int $base = self::MAX_BASE): string
608
    {
609 36
        return self::decBaseHelper(
610
            $base, static function (int $base) use ($binary) {
611 35
            $size = strlen($binary);
612 35
            $return = '0';
613 35
            for ($i = 0; $i < $size; ++$i) {
614 35
                $element = ord($binary[$i]);
615 35
                $power = BC::pow((string)$base, (string)($size - $i - 1));
616 35
                $return = BC::add($return, BC::mul((string)$element, $power));
617
            }
618
619 35
            return $return;
620 36
        }
621
        );
622
    }
623
624 14
    public static function bitOr(string $leftOperand, string $rightOperand): string
625
    {
626 14
        return self::bitOperatorHelper($leftOperand, $rightOperand, self::BIT_OPERATOR_OR);
627
    }
628
629 10
    public static function bitXor(string $leftOperand, string $rightOperand): string
630
    {
631 10
        return self::bitOperatorHelper($leftOperand, $rightOperand, self::BIT_OPERATOR_XOR);
632
    }
633
634 24
    public static function roundHalfEven(string $number, int $precision = 0): string
635
    {
636 24
        $number = self::convertScientificNotationToString($number);
637 24
        if (! self::checkIsFloat($number)) {
638
            return self::checkNumber($number);
639
        }
640
641 24
        $precessionPos = strpos($number, '.') + $precision + 1;
642 24
        if (strlen($number) <= $precessionPos) {
643 1
            return self::round($number, $precision);
644
        }
645
646 23
        if ($number[$precessionPos] !== '5') {
647 8
            return self::round($number, $precision);
648
        }
649
650 15
        $isPrevEven = $number[$precessionPos - 1] === '.'
651 4
            ? (int)$number[$precessionPos - 2] % 2 === 0
652 15
            : (int)$number[$precessionPos - 1] % 2 === 0
653
        ;
654 15
        $isNegative = self::isNegative($number);
655
656 15
        if ($isPrevEven === $isNegative) {
657 7
            return self::roundUp($number, $precision);
658
        }
659
660 8
        return self::roundDown($number, $precision);
661
    }
662
}
663