Passed
Push — master ( 0e8e4b...bf0d1d )
by kacper
01:56
created

BC::powFractional()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 13
cts 13
cp 1
rs 9.584
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 312
    public static function setScale($scale)
20
    {
21 312
        bcscale($scale);
22 312
    }
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 312
    public static function convertScientificNotationToString($number)
48
    {
49
        // check if number is in scientific notation, first use stripos as is faster then preg_match
50 312
        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 312
        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 111
    public static function pow($leftOperand, $rightOperand, $scale = null)
89
    {
90 111
        $leftOperand = self::convertScientificNotationToString($leftOperand);
91 111
        $rightOperand = self::convertScientificNotationToString($rightOperand);
92
93 111
        if (self::checkIsFloat($rightOperand)) {
94 7
            if (null === $scale) {
95 2
                return self::powFractional($leftOperand, $rightOperand);
96
            }
97
98 5
            return self::powFractional($leftOperand, $rightOperand, $scale);
99
        }
100
101 111
        if (null === $scale) {
102 39
            return bcpow($leftOperand, $rightOperand);
103
        }
104
105 75
        return bcpow($leftOperand, $rightOperand, $scale);
106
    }
107
108
    /**
109
     * @param int|string $number
110
     * @return bool
111
     */
112 215
    private static function checkIsFloat($number)
113
    {
114 215
        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 7
    private static function powFractional($leftOperand, $rightOperand, $scale = null)
124
    {
125
        // we need to increased scale to get correct results and avoid rounding error
126 7
        $increasedScale = null === $scale ? self::getScale() : $scale;
127 7
        $increasedScale *= 2;
128 7
        $decimals = explode('.', $rightOperand);
129
130 7
        return self::checkNumber(
131 7
            self::mul(
132 7
                self::exp(
133 7
                    self::mul(
134 7
                        self::log($leftOperand),
135 7
                        '0.' . $decimals[1],
136 7
                        $increasedScale
137
                    )
138
                ),
139 7
                self::pow($leftOperand, $decimals[0], $increasedScale),
140 7
                $scale
141
            )
142
        );
143
    }
144
145
    /**
146
     * @param int|string $number
147
     * @return int|string
148
     */
149 312
    private static function checkNumber($number)
150
    {
151 312
        $number = str_replace('+', '', filter_var($number, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION));
152 312
        if ('-0' === $number || !is_numeric($number)) {
153 28
            return '0';
154
        }
155
156 309
        return $number;
157
    }
158
159
    /**
160
     * @param string $leftOperand
161
     * @param string $rightOperand
162
     * @param null|int $scale
163
     * @return string
164
     */
165 120 View Code Duplication
    public static function mul($leftOperand, $rightOperand, $scale = null)
166
    {
167 120
        $leftOperand = self::convertScientificNotationToString($leftOperand);
168 120
        $rightOperand = self::convertScientificNotationToString($rightOperand);
169
170 120
        if (null === $scale) {
171 14
            return bcmul($leftOperand, $rightOperand);
172
        }
173
174 108
        return bcmul($leftOperand, $rightOperand, $scale);
175
    }
176
177
    /**
178
     * @param string $arg
179
     * @return string
180
     */
181 15
    public static function exp($arg)
182
    {
183 15
        $scale = self::DEFAULT_SCALE;
184 15
        $result = '1';
185 15
        for ($i = 299; $i > 0; $i--) {
186 15
            $result = self::add(self::mul(self::div($result, $i, $scale), $arg, $scale), 1, $scale);
187
        }
188
189 15
        return $result;
190
    }
191
192
    /**
193
     * @param string $leftOperand
194
     * @param string $rightOperand
195
     * @param null|int $scale
196
     * @return string
197
     */
198 114 View Code Duplication
    public static function add($leftOperand, $rightOperand, $scale = null)
199
    {
200 114
        $leftOperand = self::convertScientificNotationToString($leftOperand);
201 114
        $rightOperand = self::convertScientificNotationToString($rightOperand);
202
203 114
        if (null === $scale) {
204 6
            return bcadd($leftOperand, $rightOperand);
205
        }
206
207 111
        return bcadd($leftOperand, $rightOperand, $scale);
208
    }
209
210
    /**
211
     * @param string $leftOperand
212
     * @param string $rightOperand
213
     * @param null|int $scale
214
     * @return string
215
     */
216 91 View Code Duplication
    public static function div($leftOperand, $rightOperand, $scale = null)
217
    {
218 91
        $leftOperand = self::convertScientificNotationToString($leftOperand);
219 91
        $rightOperand = self::convertScientificNotationToString($rightOperand);
220
221 91
        if (null === $scale) {
222 6
            return bcdiv($leftOperand, $rightOperand);
223
        }
224
225 87
        return bcdiv($leftOperand, $rightOperand, $scale);
226
    }
227
228
    /**
229
     * @param string $arg
230
     * @return string
231
     */
232 12
    public static function log($arg)
233
    {
234 12
        $arg = self::convertScientificNotationToString($arg);
235 12
        if ($arg === '0') {
236 1
            return '-INF';
237
        }
238 11
        if (self::COMPARE_RIGHT_GRATER === self::comp($arg, '0')) {
239 1
            return 'NAN';
240
        }
241 10
        $scale = self::DEFAULT_SCALE;
242 10
        $m = (string)log($arg);
243 10
        $x = self::sub(self::div($arg, self::exp($m), $scale), '1', $scale);
244 10
        $res = '0';
245 10
        $pow = '1';
246 10
        $i = 1;
247
        do {
248 10
            $pow = self::mul($pow, $x, $scale);
249 10
            $sum = self::div($pow, $i, $scale);
250 10
            if ($i % 2 === 1) {
251 10
                $res = self::add($res, $sum, $scale);
252
            } else {
253 9
                $res = self::sub($res, $sum, $scale);
254
            }
255 10
            $i++;
256 10
        } while (self::comp($sum, '0', $scale));
257
258 10
        return self::add($res, $m, $scale);
259
    }
260
261
    /**
262
     * @param string $leftOperand
263
     * @param string $rightOperand
264
     * @param null|int $scale
265
     * @return int
266
     */
267 25
    public static function comp($leftOperand, $rightOperand, $scale = null)
268
    {
269 25
        $leftOperand = self::convertScientificNotationToString($leftOperand);
270 25
        $rightOperand = self::convertScientificNotationToString($rightOperand);
271
272 25
        if (null === $scale) {
273 14
            return bccomp($leftOperand, $rightOperand, max(strlen($leftOperand), strlen($rightOperand)));
274
        }
275
276 21
        return bccomp(
277 21
            $leftOperand,
278 21
            $rightOperand,
279 21
            $scale
280
        );
281
    }
282
283
    /**
284
     * @param string $leftOperand
285
     * @param string $rightOperand
286
     * @param null|int $scale
287
     * @return string
288
     */
289 39 View Code Duplication
    public static function sub($leftOperand, $rightOperand, $scale = null)
290
    {
291 39
        $leftOperand = self::convertScientificNotationToString($leftOperand);
292 39
        $rightOperand = self::convertScientificNotationToString($rightOperand);
293
294 39
        if (null === $scale) {
295 8
            return bcsub($leftOperand, $rightOperand);
296
        }
297
298 33
        return bcsub($leftOperand, $rightOperand, $scale);
299
    }
300
301
    /**
302
     * @param $number
303
     * @return bool
304
     */
305 112
    private static function isNegative($number)
306
    {
307 112
        return 0 === strncmp('-', $number, 1);
308
    }
309
310
    /**
311
     * @param int|string $number
312
     * @return string
313
     */
314 15
    public static function abs($number)
315
    {
316 15
        $number = self::convertScientificNotationToString($number);
317
318 15
        if (self::isNegative($number)) {
319 8
            $number = (string)substr($number, 1);
320
        }
321
322 15
        return self::checkNumber($number);
323
    }
324
325
    /**
326
     * @param int|string $min
327
     * @param int|string $max
328
     * @return string
329
     */
330 2
    public static function rand($min, $max)
331
    {
332 2
        $max = self::convertScientificNotationToString($max);
333 2
        $min = self::convertScientificNotationToString($min);
334
335 2
        $difference = self::add(self::sub($max, $min), 1);
336 2
        $randPercent = self::div(mt_rand(), mt_getrandmax(), 8);
337
338 2
        return self::add($min, self::mul($difference, $randPercent, 8), 0);
339
    }
340
341
    /**
342
     * @param array|int|string,...
343
     * @return null|string
344
     */
345 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...
346
    {
347 1
        $max = null;
348 1
        $args = func_get_args();
349 1
        if (is_array($args[0])) {
350 1
            $args = $args[0];
351
        }
352 1
        foreach ($args as $number) {
353 1
            $number = self::convertScientificNotationToString($number);
354 1
            if (null === $max) {
355 1
                $max = $number;
356 1
            } else if (self::comp($max, $number) === self::COMPARE_RIGHT_GRATER) {
357 1
                $max = $number;
358
            }
359
        }
360
361 1
        return $max;
362
    }
363
364
    /**
365
     * @param array|int|string,...
366
     * @return null|string
367
     */
368 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...
369
    {
370 1
        $min = null;
371 1
        $args = func_get_args();
372 1
        if (is_array($args[0])) {
373 1
            $args = $args[0];
374
        }
375 1
        foreach ($args as $number) {
376 1
            $number = self::convertScientificNotationToString($number);
377 1
            if (null === $min) {
378 1
                $min = $number;
379 1
            } else if (self::comp($min, $number) === self::COMPARE_LEFT_GRATER) {
380 1
                $min = $number;
381
            }
382
        }
383
384 1
        return $min;
385
    }
386
387
    /**
388
     * @param int|string $number
389
     * @param int $precision
390
     * @return string
391
     */
392 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...
393
    {
394 17
        $number = self::convertScientificNotationToString($number);
395 17
        $multiply = self::pow(10, (string)abs($precision));
396
397 17
        return $precision < 0 ?
398 4
            self::mul(
399 4
                self::floor(self::div($number, $multiply, self::getDecimalsLengthFromNumber($number))), $multiply,
400 4
                $precision
401
            ) :
402 13
            self::div(
403 13
                self::floor(self::mul($number, $multiply, self::getDecimalsLengthFromNumber($number))), $multiply,
404 17
                $precision
405
            );
406
    }
407
408
    /**
409
     * @param int|string $number
410
     * @return string
411
     */
412 59 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...
413
    {
414 59
        $number = self::convertScientificNotationToString($number);
415 59
        if (self::checkIsFloat($number) && self::checkIsFloatCleanZeros($number)) {
416 32
            $result = 0;
417 32
            if (self::isNegative($number)) {
418 7
                --$result;
419
            }
420 32
            $number = self::add($number, $result, 0);
421
        }
422
423 59
        return self::checkNumber($number);
424
    }
425
426
    /**
427
     * @param int|string $number
428
     * @return bool
429
     */
430 62
    private static function checkIsFloatCleanZeros(&$number)
431
    {
432 62
        return false !== strpos($number = rtrim(rtrim($number, '0'), '.'), '.');
433
    }
434
435
    /**
436
     * @param int|string $number
437
     * @param int $precision
438
     * @return string
439
     */
440 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...
441
    {
442 17
        $number = self::convertScientificNotationToString($number);
443 17
        $multiply = self::pow(10, (string)abs($precision));
444
445 17
        return $precision < 0 ?
446 4
            self::mul(
447 4
                self::ceil(self::div($number, $multiply, self::getDecimalsLengthFromNumber($number))), $multiply,
448 4
                $precision
449
            ) :
450 13
            self::div(
451 13
                self::ceil(self::mul($number, $multiply, self::getDecimalsLengthFromNumber($number))), $multiply,
452 17
                $precision
453
            );
454
    }
455
456
    /**
457
     * @param int|string $number
458
     * @return string
459
     */
460 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...
461
    {
462 41
        $number = self::convertScientificNotationToString($number);
463 41
        if (self::checkIsFloat($number) && self::checkIsFloatCleanZeros($number)) {
464 22
            $result = 1;
465 22
            if (self::isNegative($number)) {
466 5
                --$result;
467
            }
468 22
            $number = self::add($number, $result, 0);
469
        }
470
471 41
        return self::checkNumber($number);
472
    }
473
474
    /**
475
     * @return int
476
     */
477 5
    public static function getScale()
478
    {
479 5
        $sqrt = self::sqrt('2');
480
481 5
        return strlen(substr($sqrt, strpos($sqrt, '.') + 1));
482
    }
483
484
    /**
485
     * @param string $operand
486
     * @param null|int $scale
487
     * @return string
488
     */
489 10
    public static function sqrt($operand, $scale = null)
490
    {
491 10
        $operand = self::convertScientificNotationToString($operand);
492
493 10
        if (null === $scale) {
494 6
            return bcsqrt($operand);
495
        }
496
497 5
        return bcsqrt($operand, $scale);
498
    }
499
500
    /**
501
     * @param string $leftOperand
502
     * @param string $modulus
503
     * @param null $scale
504
     * @return string
505
     */
506 17
    public static function mod($leftOperand, $modulus, $scale = null)
507
    {
508 17
        $leftOperand = self::convertScientificNotationToString($leftOperand);
509
510
        // 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
511
512
        // bcmod in php 5.6< don't support scale and floats
513
        // let use this $x - floor($x/$y) * $y;
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
514 17
        if (null === $scale) {
515 5
            return self::sub($leftOperand, self::mul(self::floor(self::div($leftOperand, $modulus)), $modulus));
516
        }
517
518 12
        return self::sub(
519 12
            $leftOperand, self::mul(self::floor(self::div($leftOperand, $modulus, $scale)), $modulus, $scale), $scale
520
        );
521
    }
522
523
    /**
524
     * @param string $leftOperand
525
     * @param string $rightOperand
526
     * @param string $modulus
527
     * @param null|int $scale
528
     * @return string
529
     */
530 10
    public static function powMod($leftOperand, $rightOperand, $modulus, $scale = null)
531
    {
532 10
        $leftOperand = self::convertScientificNotationToString($leftOperand);
533 10
        $rightOperand = self::convertScientificNotationToString($rightOperand);
534
535
        // bcpowmod in 5.6 have don't calculate correct results if scale is empty
536 10
        if (null === $scale) {
537 4
            return self::mod(self::pow($leftOperand, $rightOperand), $modulus);
538
        }
539
540
        // cant use bcpowmod here as it don't support floats
541 6
        if (self::checkIsFloat($leftOperand) || self::checkIsFloat($rightOperand) || self::checkIsFloat($modulus)) {
542 2
            return self::mod(self::pow($leftOperand, $rightOperand, $scale), $modulus, $scale);
543
        }
544
545 4
        return bcpowmod($leftOperand, $rightOperand, $modulus, $scale);
546
    }
547
548
    /**
549
     * @param string $arg
550
     * @return string
551
     * @throws \InvalidArgumentException
552
     */
553 8
    public static function fact($arg)
554
    {
555 8
        $arg = self::convertScientificNotationToString($arg);
556
557 8
        if (self::checkIsFloat($arg)) {
558 1
            throw new \InvalidArgumentException('Number has to be an integer');
559
        }
560 7
        if (self::isNegative($arg)) {
561 1
            throw new \InvalidArgumentException('Number has to be greater than or equal to 0');
562
        }
563
564 6
        $return = '1';
565 6
        for ($i = 2; $i <= $arg; ++$i) {
566 5
            $return = self::mul($return, $i);
567
        }
568
569 6
        return $return;
570
    }
571
}
572