Completed
Pull Request — master (#23)
by kacper
05:25
created

BC::powFractional()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 12
cts 12
cp 1
rs 9.6
c 0
b 0
f 0
cc 2
nc 2
nop 3
crap 2
1
<?php
2
3
namespace BCMathExtended;
4
5
/**
6
 * Class BC
7
 * @package BCMathExtended
8
 */
9
class BC
10
{
11
    const COMPARE_EQUAL = 0;
12
    const COMPARE_LEFT_GRATER = 1;
13
    const COMPARE_RIGHT_GRATER = -1;
14
    const DEFAULT_SCALE = 100;
15
16
    /**
17
     * @param null|int $scale
18
     */
19 13
    public static function setScale($scale)
20
    {
21 13
        bcscale($scale);
22 13
    }
23
24
    /**
25
     * @param int|string $number
26
     * @param int $precision
27
     * @return string
28
     */
29 47
    public static function round($number, $precision = 0)
30
    {
31 47
        $number = self::convertScientificNotationToString($number);
32 47
        if (self::checkIsFloat($number)) {
33 36
            if (self::isNegative($number)) {
34 4
                return self::sub($number, '0.' . str_repeat('0', $precision) . '5', $precision);
35
            }
36
37 32
            return self::add($number, '0.' . str_repeat('0', $precision) . '5', $precision);
38
        }
39
40 11
        return self::checkNumber($number);
41
    }
42
43
    /**
44
     * @param int|string|float $number
45
     * @return string
46
     */
47 306
    public static function convertScientificNotationToString($number)
48
    {
49
        // check if number is in scientific notation, first use stripos as is faster then preg_match
50 306
        if (false !== stripos($number, 'E') && preg_match('/(-?(\d+\.)?\d+)E([+-]?)(\d+)/i', $number, $regs)) {
51
            // calculate final scale of number
52 59
            $scale = $regs[4] + self::getDecimalsLengthFromNumber($regs[1]);
53 59
            $pow = self::pow(10, $regs[4], $scale);
54 59
            if ('-' === $regs[3]) {
55 25
                $number = self::div($regs[1], $pow, $scale);
56
            } else {
57 34
                $number = self::mul($pow, $regs[1], $scale);
58
            }
59
            // remove unnecessary 0 from 0.000 is a 0
60 59
            $number = rtrim($number, '0');
61
            // if you remove 0 you must clean dot
62 59
            $number = rtrim($number, '.');
63
        }
64
65 306
        return self::checkNumber($number);
66
    }
67
68
    /**
69
     * @param int|string|float $number
70
     * @return int
71
     */
72 91
    public static function getDecimalsLengthFromNumber($number)
73
    {
74 91
        $check = explode('.', $number);
75 91
        if (!empty($check[1])) {
76 62
            return strlen($check[1]);
77
        }
78
79 29
        return 0;
80
    }
81
82
    /**
83
     * @param string $leftOperand
84
     * @param string $rightOperand
85
     * @param null|int $scale
86
     * @return string
87
     */
88 104
    public static function pow($leftOperand, $rightOperand, $scale = null)
89
    {
90 104
        $leftOperand = self::convertScientificNotationToString($leftOperand);
91 104
        $rightOperand = self::convertScientificNotationToString($rightOperand);
92
93 104
        if (self::checkIsFloat($rightOperand)) {
94 4
            if (null === $scale) {
95 1
                return self::powFractional($leftOperand, $rightOperand);
96
            }
97
98 3
            return self::powFractional($leftOperand, $rightOperand, $scale);
99
        }
100
101 104
        if (null === $scale) {
102 37
            return bcpow($leftOperand, $rightOperand);
103
        }
104
105 70
        return bcpow($leftOperand, $rightOperand, $scale);
106
    }
107
108
    /**
109
     * @param int|string $number
110
     * @return bool
111
     */
112 201
    private static function checkIsFloat($number)
113
    {
114 201
        return false !== strpos($number, '.');
115
    }
116
117
    /**
118
     * @param string $leftOperand
119
     * @param string $rightOperand
120
     * @param null|int $scale
121
     * @return string
122
     */
123 4
    private static function powFractional($leftOperand, $rightOperand, $scale = null)
124
    {
125
        // we need to increased scale to get correct results and avoid rounding error
126 4
        $increasedScale = null === $scale ? null : $scale * 2;
127 4
        $decimals = explode('.', $rightOperand);
128
129 4
        return self::checkNumber(
130 4
            self::mul(
131 4
                self::exp(
132 4
                    self::mul(
133 4
                        self::log($leftOperand),
134 4
                        '0.' . $decimals[1],
135 4
                        $increasedScale
136
                    )
137
                ),
138 4
                self::pow($leftOperand, $decimals[0], $increasedScale),
139 4
                $scale
140
            )
141
        );
142
    }
143
144
    /**
145
     * @param int|string $number
146
     * @return int|string
147
     */
148 306
    private static function checkNumber($number)
149
    {
150 306
        $number = str_replace('+', '', filter_var($number, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION));
151 306
        if ('-0' === $number || !is_numeric($number)) {
152 28
            return '0';
153
        }
154
155 303
        return $number;
156
    }
157
158
    /**
159
     * @param string $leftOperand
160
     * @param string $rightOperand
161
     * @param null|int $scale
162
     * @return string
163
     */
164 109 View Code Duplication
    public static function mul($leftOperand, $rightOperand, $scale = null)
165
    {
166 109
        $leftOperand = self::convertScientificNotationToString($leftOperand);
167 109
        $rightOperand = self::convertScientificNotationToString($rightOperand);
168
169 109
        if (null === $scale) {
170 11
            return bcmul($leftOperand, $rightOperand);
171
        }
172
173 101
        return bcmul($leftOperand, $rightOperand, $scale);
174
    }
175
176
    /**
177
     * @param string $arg
178
     * @return string
179
     */
180 12
    public static function exp($arg)
181
    {
182 12
        $scale = self::DEFAULT_SCALE;
183 12
        $result = '1';
184 12
        for ($i = 299; $i > 0; $i--) {
185 12
            $result = self::add(self::mul(self::div($result, $i, $scale), $arg, $scale), 1, $scale);
186
        }
187
188 12
        return $result;
189
    }
190
191
    /**
192
     * @param string $leftOperand
193
     * @param string $rightOperand
194
     * @param null|int $scale
195
     * @return string
196
     */
197 107 View Code Duplication
    public static function add($leftOperand, $rightOperand, $scale = null)
198
    {
199 107
        $leftOperand = self::convertScientificNotationToString($leftOperand);
200 107
        $rightOperand = self::convertScientificNotationToString($rightOperand);
201
202 107
        if (null === $scale) {
203 6
            return bcadd($leftOperand, $rightOperand);
204
        }
205
206 104
        return bcadd($leftOperand, $rightOperand, $scale);
207
    }
208
209
    /**
210
     * @param string $leftOperand
211
     * @param string $rightOperand
212
     * @param null|int $scale
213
     * @return string
214
     */
215 81 View Code Duplication
    public static function div($leftOperand, $rightOperand, $scale = null)
216
    {
217 81
        $leftOperand = self::convertScientificNotationToString($leftOperand);
218 81
        $rightOperand = self::convertScientificNotationToString($rightOperand);
219
220 81
        if (null === $scale) {
221 3
            return bcdiv($leftOperand, $rightOperand);
222
        }
223
224 81
        return bcdiv($leftOperand, $rightOperand, $scale);
225
    }
226
227
    /**
228
     * @param string $arg
229
     * @return string
230
     */
231 9
    public static function log($arg)
232
    {
233 9
        $arg = self::convertScientificNotationToString($arg);
234 9
        if ($arg === '0') {
235 1
            return '-INF';
236
        }
237 8
        if (self::COMPARE_RIGHT_GRATER === self::comp($arg, '0')) {
238 1
            return 'NAN';
239
        }
240 7
        $scale = self::DEFAULT_SCALE;
241 7
        $m = (string)log($arg);
242 7
        $x = self::sub(self::div($arg, self::exp($m), $scale), '1', $scale);
243 7
        $res = '0';
244 7
        $pow = '1';
245 7
        $i = 1;
246
        do {
247 7
            $pow = self::mul($pow, $x, $scale);
248 7
            $sum = self::div($pow, $i, $scale);
249 7
            if ($i % 2 === 1) {
250 7
                $res = self::add($res, $sum, $scale);
251
            } else {
252 6
                $res = self::sub($res, $sum, $scale);
253
            }
254 7
            $i++;
255 7
        } while (self::comp($sum, '0', $scale));
256
257 7
        return self::add($res, $m, $scale);
258
    }
259
260
    /**
261
     * @param string $leftOperand
262
     * @param string $rightOperand
263
     * @param null|int $scale
264
     * @return int
265
     */
266 22
    public static function comp($leftOperand, $rightOperand, $scale = null)
267
    {
268 22
        $leftOperand = self::convertScientificNotationToString($leftOperand);
269 22
        $rightOperand = self::convertScientificNotationToString($rightOperand);
270
271 22
        if (null === $scale) {
272 11
            return bccomp($leftOperand, $rightOperand, max(strlen($leftOperand), strlen($rightOperand)));
273
        }
274
275 18
        return bccomp(
276 18
            $leftOperand,
277 18
            $rightOperand,
278 18
            $scale
279
        );
280
    }
281
282
    /**
283
     * @param string $leftOperand
284
     * @param string $rightOperand
285
     * @param null|int $scale
286
     * @return string
287
     */
288 28 View Code Duplication
    public static function sub($leftOperand, $rightOperand, $scale = null)
289
    {
290 28
        $leftOperand = self::convertScientificNotationToString($leftOperand);
291 28
        $rightOperand = self::convertScientificNotationToString($rightOperand);
292
293 28
        if (null === $scale) {
294 5
            return bcsub($leftOperand, $rightOperand);
295
        }
296
297 26
        return bcsub($leftOperand, $rightOperand, $scale);
298
    }
299
300
    /**
301
     * @param $number
302
     * @return bool
303
     */
304 106
    private static function isNegative($number)
305
    {
306 106
        return 0 === strncmp('-', $number, 1);
307
    }
308
309
    /**
310
     * @param int|string $number
311
     * @return string
312
     */
313 15
    public static function abs($number)
314
    {
315 15
        $number = self::convertScientificNotationToString($number);
316
317 15
        if (self::isNegative($number)) {
318 8
            $number = (string)substr($number, 1);
319
        }
320
321 15
        return self::checkNumber($number);
322
    }
323
324
    /**
325
     * @param int|string $min
326
     * @param int|string $max
327
     * @return string
328
     */
329 2
    public static function rand($min, $max)
330
    {
331 2
        $max = self::convertScientificNotationToString($max);
332 2
        $min = self::convertScientificNotationToString($min);
333
334 2
        $difference = self::add(self::sub($max, $min), 1);
335 2
        $randPercent = self::div(mt_rand(), mt_getrandmax(), 8);
336
337 2
        return self::add($min, self::mul($difference, $randPercent, 8), 0);
338
    }
339
340
    /**
341
     * @param array|int|string,...
342
     * @return null|string
343
     */
344 1 View Code Duplication
    public static function max()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
345
    {
346 1
        $max = null;
347 1
        $args = func_get_args();
348 1
        if (is_array($args[0])) {
349 1
            $args = $args[0];
350
        }
351 1
        foreach ($args as $number) {
352 1
            $number = self::convertScientificNotationToString($number);
353 1
            if (null === $max) {
354 1
                $max = $number;
355 1
            } else if (self::comp($max, $number) === self::COMPARE_RIGHT_GRATER) {
356 1
                $max = $number;
357
            }
358
        }
359
360 1
        return $max;
361
    }
362
363
    /**
364
     * @param array|int|string,...
365
     * @return null|string
366
     */
367 1 View Code Duplication
    public static function min()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
368
    {
369 1
        $min = null;
370 1
        $args = func_get_args();
371 1
        if (is_array($args[0])) {
372 1
            $args = $args[0];
373
        }
374 1
        foreach ($args as $number) {
375 1
            $number = self::convertScientificNotationToString($number);
376 1
            if (null === $min) {
377 1
                $min = $number;
378 1
            } else if (self::comp($min, $number) === self::COMPARE_LEFT_GRATER) {
379 1
                $min = $number;
380
            }
381
        }
382
383 1
        return $min;
384
    }
385
386
    /**
387
     * @param int|string $number
388
     * @param int $precision
389
     * @return string
390
     */
391 17 View Code Duplication
    public static function roundDown($number, $precision = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
392
    {
393 17
        $number = self::convertScientificNotationToString($number);
394 17
        $multiply = self::pow(10, (string)abs($precision));
395
396 17
        return $precision < 0 ?
397 4
            self::mul(
398 4
                self::floor(self::div($number, $multiply, self::getDecimalsLengthFromNumber($number))), $multiply,
399 4
                $precision
400
            ) :
401 13
            self::div(
402 13
                self::floor(self::mul($number, $multiply, self::getDecimalsLengthFromNumber($number))), $multiply,
403 17
                $precision
404
            );
405
    }
406
407
    /**
408
     * @param int|string $number
409
     * @return string
410
     */
411 49 View Code Duplication
    public static function floor($number)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
412
    {
413 49
        $number = self::convertScientificNotationToString($number);
414 49
        if (self::checkIsFloat($number) && self::checkIsFloatCleanZeros($number)) {
415 26
            $result = 0;
416 26
            if (self::isNegative($number)) {
417 7
                --$result;
418
            }
419 26
            $number = self::add($number, $result, 0);
420
        }
421
422 49
        return self::checkNumber($number);
423
    }
424
425
    /**
426
     * @param int|string $number
427
     * @return bool
428
     */
429 56
    private static function checkIsFloatCleanZeros(&$number)
430
    {
431 56
        return false !== strpos($number = rtrim(rtrim($number, '0'), '.'), '.');
432
    }
433
434
    /**
435
     * @param int|string $number
436
     * @param int $precision
437
     * @return string
438
     */
439 17 View Code Duplication
    public static function roundUp($number, $precision = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
440
    {
441 17
        $number = self::convertScientificNotationToString($number);
442 17
        $multiply = self::pow(10, (string)abs($precision));
443
444 17
        return $precision < 0 ?
445 4
            self::mul(
446 4
                self::ceil(self::div($number, $multiply, self::getDecimalsLengthFromNumber($number))), $multiply,
447 4
                $precision
448
            ) :
449 13
            self::div(
450 13
                self::ceil(self::mul($number, $multiply, self::getDecimalsLengthFromNumber($number))), $multiply,
451 17
                $precision
452
            );
453
    }
454
455
    /**
456
     * @param int|string $number
457
     * @return string
458
     */
459 41 View Code Duplication
    public static function ceil($number)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
460
    {
461 41
        $number = self::convertScientificNotationToString($number);
462 41
        if (self::checkIsFloat($number) && self::checkIsFloatCleanZeros($number)) {
463 22
            $result = 1;
464 22
            if (self::isNegative($number)) {
465 5
                --$result;
466
            }
467 22
            $number = self::add($number, $result, 0);
468
        }
469
470 41
        return self::checkNumber($number);
471
    }
472
473
    /**
474
     * @return int
475
     */
476 3
    public static function getScale()
477
    {
478 3
        $sqrt = self::sqrt('2');
479
480 3
        return strlen(substr($sqrt, strpos($sqrt, '.') + 1));
481
    }
482
483
    /**
484
     * @param string $operand
485
     * @param null|int $scale
486
     * @return string
487
     */
488 8
    public static function sqrt($operand, $scale = null)
489
    {
490 8
        $operand = self::convertScientificNotationToString($operand);
491
492 8
        if (null === $scale) {
493 4
            return bcsqrt($operand);
494
        }
495
496 5
        return bcsqrt($operand, $scale);
497
    }
498
499
    /**
500
     * @param string $leftOperand
501
     * @param string $modulus
502
     * @param null|int $scale
503
     * @return string
504
     */
505 7
    public static function fmod($leftOperand, $modulus, $scale = null)
506
    {
507 7
        $leftOperand = self::convertScientificNotationToString($leftOperand);
508
509
        // mod(a, b) = a - b * floor(a/b)
510 7
        return self::sub(
511 7
            $leftOperand,
512 7
            self::mul(
513 7
                $modulus,
514 7
                self::floor(self::div($leftOperand, $modulus, $scale)),
515 7
                $scale
516
            ),
517 7
            $scale
518
        );
519
    }
520
521
    /**
522
     * @param string $leftOperand
523
     * @param string $modulus
524
     * @return string
525
     */
526 4
    public static function mod($leftOperand, $modulus)
527
    {
528 4
        $leftOperand = self::convertScientificNotationToString($leftOperand);
529
530
        // @codeCoverageIgnoreStart
531
        if (version_compare(PHP_VERSION, '7.2.0') >= 0) {
532
            return bcmod(
533
                $leftOperand,
534
                $modulus,
535
                0
536
            );
537
        }
538
539
        // @codeCoverageIgnoreEnd
540
541 4
        return bcmod(
542 4
            $leftOperand,
543 4
            $modulus
544
        );
545
    }
546
547
    /**
548
     * @param string $leftOperand
549
     * @param string $rightOperand
550
     * @param string $modulus
551
     * @param null|int $scale
552
     * @return string
553
     */
554 5 View Code Duplication
    public static function powMod($leftOperand, $rightOperand, $modulus, $scale = null)
555
    {
556 5
        $leftOperand = self::convertScientificNotationToString($leftOperand);
557 5
        $rightOperand = self::convertScientificNotationToString($rightOperand);
558
559 5
        if (null === $scale) {
560 1
            return bcpowmod($leftOperand, $rightOperand, $modulus);
561
        }
562
563 4
        return bcpowmod($leftOperand, $rightOperand, $modulus, $scale);
564
    }
565
566
    /**
567
     * @param string $arg
568
     * @return string
569
     * @throws \InvalidArgumentException
570
     */
571 8
    public static function fact($arg)
572
    {
573 8
        $arg = self::convertScientificNotationToString($arg);
574
575 8
        if (self::checkIsFloat($arg)) {
576 1
            throw new \InvalidArgumentException('Number has to be an integer');
577
        }
578 7
        if (self::isNegative($arg)) {
579 1
            throw new \InvalidArgumentException('Number has to be greater than or equal to 0');
580
        }
581
582 6
        $return = '1';
583 6
        for ($i = 2; $i <= $arg; ++$i) {
584 5
            $return = self::mul($return, $i);
585
        }
586
587 6
        return $return;
588
    }
589
}
590