Completed
Push — master ( 7a222d...ef221c )
by Andreu
06:00
created

Decimal::arccsc()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 22
ccs 14
cts 14
cp 1
rs 8.6737
cc 6
eloc 14
nc 7
nop 1
crap 6
1
<?php
2
3
namespace Litipk\BigNumbers;
4
5
use Litipk\BigNumbers\DecimalConstants as DecimalConstants;
6
use Litipk\BigNumbers\InfiniteDecimal as InfiniteDecimal;
7
8
use Litipk\Exceptions\NotImplementedException as NotImplementedException;
9
use Litipk\Exceptions\InvalidArgumentTypeException as InvalidArgumentTypeException;
10
11
/**
12
 * Immutable object that represents a rational number
13
 *
14
 * @author Andreu Correa Casablanca <[email protected]>
15
 */
16
class Decimal
17
{
18
    /**
19
     * Internal numeric value
20
     * @var string
21
     */
22
    protected $value;
23
24
    /**
25
     * Number of digits behind the point
26
     * @var integer
27
     */
28
    private $scale;
29
30
    /**
31
     * Private constructor
32
     * @param integer $scale
33
     * @param string $value
34
     */
35 169
    private function __construct($value, $scale)
36
    {
37 169
        $this->value = $value;
38 169
        $this->scale = $scale;
39 169
    }
40
41
    /**
42
     * Private clone method
43
     */
44
    private function __clone()
45
    {
46
47
    }
48
49
    /**
50
     * Returns a "Positive Infinite" object
51
     * @return Decimal
52
     */
53 14
    public static function getPositiveInfinite()
54
    {
55 14
        return InfiniteDecimal::getPositiveInfinite();
56
    }
57
58
    /**
59
     * Returns a "Negative Infinite" object
60
     * @return Decimal
61
     */
62 14
    public static function getNegativeInfinite()
63
    {
64 14
        return InfiniteDecimal::getNegativeInfinite();
65
    }
66
67
    /**
68
     * Decimal "constructor".
69
     *
70
     * @param  mixed   $value
71
     * @param  integer $scale
72
     * @param  boolean $removeZeros If true then removes trailing zeros from the number representation
73
     * @return Decimal
74
     */
75 5
    public static function create($value, $scale = null, $removeZeros = false)
76
    {
77 5
        if (is_int($value)) {
78 1
            return self::fromInteger($value);
79 4
        } elseif (is_float($value)) {
80 1
            return self::fromFloat($value, $scale, $removeZeros);
81 3
        } elseif (is_string($value)) {
82 1
            return self::fromString($value, $scale, $removeZeros);
83
        } elseif ($value instanceof Decimal) {
84 1
            return self::fromDecimal($value, $scale);
85
        } else {
86 1
            throw new InvalidArgumentTypeException(
87 1
                ['int', 'float', 'string', 'Decimal'],
88 1
                is_object($value) ? get_class($value) : gettype($value),
89 1
                'Invalid argument type.'
90
            );
91
        }
92
    }
93
94
    /**
95
     * @param  integer $intValue
96
     * @return Decimal
97
     */
98 112
    public static function fromInteger($intValue)
99
    {
100 112
        self::paramsValidation($intValue, null);
101
102 111
        if (!is_int($intValue)) {
103 1
            throw new InvalidArgumentTypeException(
104 1
                ['int'],
105 1
                is_object($intValue) ? get_class($intValue) : gettype($intValue),
106 1
                '$intValue must be of type int'
107
            );
108
        }
109
110 110
        return new static((string)$intValue, 0);
111
    }
112
113
    /**
114
     * @param  float   $fltValue
115
     * @param  integer $scale
116
     * @param  boolean $removeZeros If true then removes trailing zeros from the number representation
117
     * @return Decimal
118
     */
119 35
    public static function fromFloat($fltValue, $scale = null, $removeZeros = false)
120
    {
121 35
        self::paramsValidation($fltValue, $scale);
122
123 35
        if (!is_float($fltValue)) {
124 1
            throw new InvalidArgumentTypeException(
125 1
                ['float'],
126 1
                is_object($fltValue) ?
127
                    get_class($fltValue) :
128 1
                    gettype($fltValue),
129 1
                '$fltValue must be of type float'
130
            );
131 34
        } elseif ($fltValue === INF) {
132 2
            return InfiniteDecimal::getPositiveInfinite();
133 34
        } elseif ($fltValue === -INF) {
134 2
            return InfiniteDecimal::getNegativeInfinite();
135 33
        } elseif (is_nan($fltValue)) {
136 1
            throw new \DomainException(
137 1
                "To ensure consistency, this class doesn't handle NaN objects."
138
            );
139
        }
140
141 32
        $defaultScale = 16;
142
143 32
        $strValue = (string) $fltValue;
144 32
        if (preg_match("/^ (?P<int> \d*) (?: \. (?P<dec> \d+) ) E (?P<sign>[\+\-]) (?P<exp>\d+) $/x", $strValue, $capture)) {
145 7
            if ($scale === null) {
146 4
                if ($capture['sign'] == '-') {
147 3
                    $scale = $capture['exp'] + strlen($capture['dec']);
148
                } else {
149 1
                    $scale = $defaultScale;
150
                }
151
            }
152 7
            $strValue = number_format($fltValue, $scale, '.', '');
153
        }
154
155 32
        if ($scale === null) {
156 25
            $scale = $defaultScale;
157
        }
158
159 32
        if ($removeZeros) {
160 1
            $strValue = self::removeTrailingZeros($strValue, $scale);
161
        }
162
163 32
        return new static($strValue, $scale);
164
    }
165
166
    /**
167
     * @param  string  $strValue
168
     * @param  integer $scale
169
     * @param  boolean $removeZeros If true then removes trailing zeros from the number representation
170
     * @return Decimal
171
     */
172 131
    public static function fromString($strValue, $scale = null, $removeZeros = false)
173
    {
174 131
        self::paramsValidation($strValue, $scale);
175
176 129
        if (!is_string($strValue)) {
177 1
            throw new InvalidArgumentTypeException(
178 1
                ['string'],
179 1
                is_object($strValue) ? get_class($strValue) : gettype($strValue),
180 1
                '$strVlue must be of type string.'
181
            );
182 128
        } elseif (preg_match('/^([+\-]?)0*(([1-9][0-9]*|[0-9])(\.[0-9]+)?)$/', $strValue, $captures) === 1) {
183
184
            // Now it's time to strip leading zeros in order to normalize inner values
185 122
            $value = self::normalizeSign($captures[1]) . $captures[2];
186 122
            $min_scale = isset($captures[4]) ? max(0, strlen($captures[4]) - 1) : 0;
187
188 9
        } elseif (preg_match('/([+\-]?)0*([0-9](\.[0-9]+)?)[eE]([+\-]?)(\d+)/', $strValue, $captures) === 1) {
189
190 7
            $mantissa_scale = max(strlen($captures[3]) - 1, 0);
191
192 7
            $exp_val = (int)$captures[5];
193
194 7
            if (self::normalizeSign($captures[4]) === '') {
195 5
                $min_scale = max($mantissa_scale - $exp_val, 0);
196 5
                $tmp_multiplier = bcpow(10, $exp_val);
197
            } else {
198 2
                $min_scale = $mantissa_scale + $exp_val;
199 2
                $tmp_multiplier = bcpow(10, -$exp_val, $exp_val);
200
            }
201
202 7
            $value = self::normalizeSign($captures[1]) . bcmul(
203 7
                $captures[2],
204
                $tmp_multiplier,
205 7
                max($min_scale, $scale !== null ? $scale : 0)
206
            );
207
208 2
        } else if (preg_match('/([+\-]?)(inf|Inf|INF)/', $strValue, $captures) === 1) {
209 1
            if ($captures[1] === '-') {
210 1
                return InfiniteDecimal::getNegativeInfinite();
211
            } else {
212 1
                return InfiniteDecimal::getPositiveInfinite();
213
            }
214
        } else {
215 1
            throw new \InvalidArgumentException(
216 1
                '$strValue must be a string that represents uniquely a float point number.'
217
            );
218
        }
219
220 126
        $scale = ($scale!==null) ? $scale : $min_scale;
221 126
        if ($scale < $min_scale) {
222 65
            $value = self::innerRound($value, $scale);
223
        }
224 126
        if ($removeZeros) {
225
            $value = self::removeTrailingZeros($value, $scale);
226
        }
227
228 126
        return new static($value, $scale);
229
    }
230
231
    /**
232
     * Constructs a new Decimal object based on a previous one,
233
     * but changing it's $scale property.
234
     *
235
     * @param  Decimal  $decValue
236
     * @param  integer  $scale
237
     * @return Decimal
238
     */
239 3
    public static function fromDecimal(Decimal $decValue, $scale = null)
240
    {
241 3
        self::paramsValidation($decValue, $scale);
242
243
        // This block protect us from unnecessary additional instances
244 3
        if ($scale === null || $scale >= $decValue->scale || $decValue->isInfinite()) {
245 3
            return $decValue;
246
        }
247
248 2
        return new static(
249 2
            self::innerRound($decValue->value, $scale),
250
            $scale
251
        );
252
    }
253
254
    /**
255
     * Adds two Decimal objects
256
     * @param  Decimal $b
257
     * @param  integer $scale
258
     * @return Decimal
259
     */
260 41
    public function add(Decimal $b, $scale = null)
261
    {
262 41
        self::paramsValidation($b, $scale);
263
264 41
        if ($b->isInfinite()) {
265 1
            return $b;
266
        }
267
268 40
        return self::fromString(
269 40
            bcadd($this->value, $b->value, max($this->scale, $b->scale)),
270
            $scale
271
        );
272
    }
273
274
    /**
275
     * Subtracts two BigNumber objects
276
     * @param  Decimal $b
277
     * @param  integer $scale
278
     * @return Decimal
279
     */
280 34
    public function sub(Decimal $b, $scale = null)
281
    {
282 34
        self::paramsValidation($b, $scale);
283
284 34
        if ($b->isInfinite()) {
285 1
            return $b->additiveInverse();
286
        }
287
288 33
        return self::fromString(
289 33
            bcsub($this->value, $b->value, max($this->scale, $b->scale)),
290
            $scale
291
        );
292
    }
293
294
    /**
295
     * Multiplies two BigNumber objects
296
     * @param  Decimal $b
297
     * @param  integer $scale
298
     * @return Decimal
299
     */
300 51
    public function mul(Decimal $b, $scale = null)
301
    {
302 51
        self::paramsValidation($b, $scale);
303
304 50
        if ($b->isInfinite()) {
305 3
            return $b->mul($this);
306 47
        } elseif ($b->isZero()) {
307 1
            return DecimalConstants::Zero();
308
        }
309
310 47
        return self::fromString(
311 47
            bcmul($this->value, $b->value, $this->scale + $b->scale),
312
            $scale
313
        );
314
    }
315
316
    /**
317
     * Divides the object by $b .
318
     * Warning: div with $scale == 0 is not the same as
319
     *          integer division because it rounds the
320
     *          last digit in order to minimize the error.
321
     *
322
     * @param  Decimal $b
323
     * @param  integer $scale
324
     * @return Decimal
325
     */
326 61
    public function div(Decimal $b, $scale = null)
327
    {
328 61
        self::paramsValidation($b, $scale);
329
330 61
        if ($b->isZero()) {
331 1
            throw new \DomainException("Division by zero is not allowed.");
332 61
        } elseif ($this->isZero() || $b->isInfinite()) {
333 2
            return DecimalConstants::Zero();
334
        } else {
335 59
            if ($scale !== null) {
336 54
                $divscale = $scale;
337
            } else {
338
                // $divscale is calculated in order to maintain a reasonable precision
339 19
                $this_abs = $this->abs();
340 19
                $b_abs    = $b->abs();
341
342
                $log10_result =
343 19
                    self::innerLog10($this_abs->value, $this_abs->scale, 1) -
344 19
                    self::innerLog10($b_abs->value, $b_abs->scale, 1);
345
346 19
                $divscale = (int)max(
347 19
                    $this->scale + $b->scale,
348 19
                    max(
349 19
                        self::countSignificativeDigits($this, $this_abs),
350 19
                        self::countSignificativeDigits($b, $b_abs)
351 19
                    ) - max(ceil($log10_result), 0),
352 19
                    ceil(-$log10_result) + 1
353
                );
354
            }
355
356 59
            return self::fromString(
357 59
                bcdiv($this->value, $b->value, $divscale+1), $divscale
358
            );
359
        }
360
    }
361
362
    /**
363
     * Returns the square root of this object
364
     * @param  integer $scale
365
     * @return Decimal
366
     */
367 4
    public function sqrt($scale = null)
368
    {
369 4
        if ($this->isNegative()) {
370 1
            throw new \DomainException(
371 1
                "Decimal can't handle square roots of negative numbers (it's only for real numbers)."
372
            );
373 3
        } elseif ($this->isZero()) {
374 1
            return DecimalConstants::Zero();
375
        }
376
377 3
        $sqrt_scale = ($scale !== null ? $scale : $this->scale);
378
379 3
        return self::fromString(
380 3
            bcsqrt($this->value, $sqrt_scale+1),
381
            $sqrt_scale
382
        );
383
    }
384
385
    /**
386
     * Powers this value to $b
387
     *
388
     * @param  Decimal  $b      exponent
389
     * @param  integer  $scale
390
     * @return Decimal
391
     */
392 10
    public function pow(Decimal $b, $scale = null)
393
    {
394 10
        if ($this->isZero()) {
395 2
            if ($b->isPositive()) {
396 1
                return Decimal::fromDecimal($this, $scale);
397
            } else {
398 1
                throw new \DomainException(
399 1
                    "zero can't be powered to zero or negative numbers."
400
                );
401
            }
402 8
        } elseif ($b->isZero()) {
403 1
            return DecimalConstants::One();
404 7
        } else if ($b->isNegative()) {
405 2
            return DecimalConstants::One()->div(
406 2
                $this->pow($b->additiveInverse()), $scale
407
            );
408 7
        } elseif ($b->scale == 0) {
409 4
            $pow_scale = $scale === null ?
410 4
                max($this->scale, $b->scale) : max($this->scale, $b->scale, $scale);
411
412 4
            return self::fromString(
413 4
                bcpow($this->value, $b->value, $pow_scale+1),
414
                $pow_scale
415
            );
416
        } else {
417 4
            if ($this->isPositive()) {
418 3
                $pow_scale = $scale === null ?
419 3
                    max($this->scale, $b->scale) : max($this->scale, $b->scale, $scale);
420
421 3
                $truncated_b = bcadd($b->value, '0', 0);
422 3
                $remaining_b = bcsub($b->value, $truncated_b, $b->scale);
423
424 3
                $first_pow_approx = bcpow($this->value, $truncated_b, $pow_scale+1);
425 3
                $intermediate_root = self::innerPowWithLittleExponent(
426 3
                    $this->value,
427
                    $remaining_b,
428 3
                    $b->scale,
429 3
                    $pow_scale+1
430
                );
431
432 3
                return Decimal::fromString(
433 3
                    bcmul($first_pow_approx, $intermediate_root, $pow_scale+1),
434
                    $pow_scale
435
                );
436
            } else { // elseif ($this->isNegative())
437 1
                if ($b->isInteger()) {
438
                    if (preg_match('/^[+\-]?[0-9]*[02468](\.0+)?$/', $b->value, $captures) === 1) {
439
                        // $b is an even number
440
                        return $this->additiveInverse()->pow($b, $scale);
441
                    } else {
442
                        // $b is an odd number
443
                        return $this->additiveInverse()->pow($b, $scale)->additiveInverse();
444
                    }
445
                }
446
447 1
                throw new NotImplementedException(
448
                    "Usually negative numbers can't be powered to non integer numbers. " .
449 1
                    "The cases where is possible are not implemented."
450
                );
451
            }
452
        }
453
    }
454
455
    /**
456
     * Returns the object's logarithm in base 10
457
     * @param  integer $scale
458
     * @return Decimal
459
     */
460 5
    public function log10($scale = null)
461
    {
462 5
        if ($this->isNegative()) {
463 1
            throw new \DomainException(
464 1
                "Decimal can't handle logarithms of negative numbers (it's only for real numbers)."
465
            );
466 4
        } elseif ($this->isZero()) {
467 1
            return InfiniteDecimal::getNegativeInfinite();
468
        }
469
470 3
        return self::fromString(
471 3
            self::innerLog10($this->value, $this->scale, $scale !== null ? $scale+1 : $this->scale+1),
472
            $scale
473
        );
474
    }
475
476
    /**
477
     * @param  integer $scale
478
     * @return boolean
479
     */
480 103
    public function isZero($scale = null)
481
    {
482 103
        $cmp_scale = $scale !== null ? $scale : $this->scale;
483
484 103
        return (bccomp(self::innerRound($this->value, $cmp_scale), '0', $cmp_scale) === 0);
485
    }
486
487
    /**
488
     * @return boolean
489
     */
490 35
    public function isPositive()
491
    {
492 35
        return ($this->value[0] !== '-' && !$this->isZero());
493
    }
494
495
    /**
496
     * @return boolean
497
     */
498 71
    public function isNegative()
499
    {
500 71
        return ($this->value[0] === '-');
501
    }
502
503
    /**
504
     * @return boolean
505
     */
506 4
    public function isInteger()
507
    {
508 4
        return (preg_match('/^[+\-]?[0-9]+(\.0+)?$/', $this->value, $captures) === 1);
509
    }
510
511
    /**
512
     * @return boolean
513
     */
514 129
    public function isInfinite()
515
    {
516 129
        return false;
517
    }
518
519
    /**
520
     * Equality comparison between this object and $b
521
     * @param  Decimal $b
522
     * @param integer $scale
523
     * @return boolean
524
     */
525 117
    public function equals(Decimal $b, $scale = null)
526
    {
527 117
        self::paramsValidation($b, $scale);
528
529 117
        if ($this === $b) {
530 4
            return true;
531 114
        } elseif ($b->isInfinite()) {
532
            return false;
533
        } else {
534 114
            $cmp_scale = $scale !== null ? $scale : max($this->scale, $b->scale);
535
536
            return (
537 114
                bccomp(
538 114
                    self::innerRound($this->value, $cmp_scale),
539 114
                    self::innerRound($b->value, $cmp_scale),
540
                    $cmp_scale
541 114
                ) == 0
542
            );
543
        }
544
    }
545
546
    /**
547
     * $this > $b : returns 1 , $this < $b : returns -1 , $this == $b : returns 0
548
     *
549
     * @param  Decimal $b
550
     * @param  integer $scale
551
     * @return integer
552
     */
553 42
    public function comp(Decimal $b, $scale = null)
554
    {
555 42
        self::paramsValidation($b, $scale);
556
557 42
        if ($this === $b) {
558 9
            return 0;
559 41
        } elseif ($b->isInfinite()) {
560 1
            return -$b->comp($this);
561
        }
562
563 40
        $cmp_scale = $scale !== null ? $scale : max($this->scale, $b->scale);
564
565 40
        return bccomp(
566 40
            self::innerRound($this->value, $cmp_scale),
567 40
            self::innerRound($b->value, $cmp_scale),
568
            $cmp_scale
569
        );
570
    }
571
572
    /**
573
     * Returns the element's additive inverse.
574
     * @return Decimal
575
     */
576 15
    public function additiveInverse()
577
    {
578 15
        if ($this->isZero()) {
579 1
            return $this;
580 14
        } elseif ($this->isNegative()) {
581 12
            $value = substr($this->value, 1);
582
        } else { // if ($this->isPositive()) {
583 2
            $value = '-' . $this->value;
584
        }
585
586 14
        return new static($value, $this->scale);
587
    }
588
589
590
    /**
591
     * "Rounds" the Decimal to have at most $scale digits after the point
592
     * @param  integer $scale
593
     * @return Decimal
594
     */
595 52
    public function round($scale = 0)
596
    {
597 52
        if ($scale >= $this->scale) {
598 21
            return $this;
599
        }
600
601 51
        return self::fromString(self::innerRound($this->value, $scale));
602
    }
603
604
    /**
605
     * "Ceils" the Decimal to have at most $scale digits after the point
606
     * @param  integer $scale
607
     * @return Decimal
608
     */
609 4
    public function ceil($scale = 0)
610
    {
611 4
        if ($scale >= $this->scale) {
612 1
            return $this;
613
        }
614
615 3
        if ($this->isNegative()) {
616 1
            return self::fromString(bcadd($this->value, '0', $scale));
617
        }
618
619 2
        return $this->innerTruncate($scale);
620
    }
621
622 28
    private function innerTruncate($scale = 0, $ceil = true)
623
    {
624 28
        $rounded = bcadd($this->value, '0', $scale);
625
626 28
        $rlen = strlen($rounded);
627 28
        $tlen = strlen($this->value);
628
629 28
        $mustTruncate = false;
630 28
        for ($i=$tlen-1; $i >= $rlen; $i--) {
631 28
            if ((int)$this->value[$i] > 0) {
632 28
                $mustTruncate = true;
633 28
                break;
634
            }
635
        }
636
637 28
        if ($mustTruncate) {
638 28
            $rounded = $ceil ?
639 2
                bcadd($rounded, bcpow('10', -$scale, $scale), $scale) :
640 28
                bcsub($rounded, bcpow('10', -$scale, $scale), $scale);
641
        }
642
643 28
        return self::fromString($rounded, $scale);
644
    }
645
646
    /**
647
     * "Floors" the Decimal to have at most $scale digits after the point
648
     * @param  integer $scale
649
     * @return Decimal
650
     */
651 47
    public function floor($scale = 0)
652
    {
653 47
        if ($scale >= $this->scale) {
654 9
            return $this;
655
        }
656
657 38
        if ($this->isNegative()) {
658 26
            return $this->innerTruncate($scale, false);
659
        }
660
661 35
        return self::fromString(bcadd($this->value, '0', $scale));
662
    }
663
664
    /**
665
     * Returns the absolute value (always a positive number)
666
     * @return Decimal
667
     */
668 21
    public function abs()
669
    {
670 21
        if ($this->isZero() || $this->isPositive()) {
671 19
            return $this;
672
        }
673
674 11
        return $this->additiveInverse();
675
    }
676
677
    /**
678
     * Calculate modulo with a decimal
679
     * @param Decimal $d
680
     * @param integer $scale
681
     * @return $this % $d
682
     */
683 27
    public function mod(Decimal $d, $scale = null)
684
    {
685 27
        $div = $this->div($d, 1)->floor();
686 27
        return $this->sub($div->mul($d), $scale);
687
    }
688
689
    /**
690
     * Calculates the sine of this method with the highest possible accuracy
691
     * Note that accuracy is limited by the accuracy of predefined PI;
692
     *
693
     * @param integer $scale
694
     * @return Decimal sin($this)
695
     */
696 13
    public function sin($scale = null)
697
    {
698
        // First normalise the number in the [0, 2PI] domain
699 13
        $x = $this->mod(DecimalConstants::PI()->mul(Decimal::fromString("2")));
700
701
        // PI has only 32 significant numbers
702 13
        $scale = ($scale === null) ? 32 : $scale;
703
704 13
        return self::factorialSerie(
705
            $x,
706 13
            DecimalConstants::zero(),
707
            function ($i) {
708 13
                return ($i % 2 === 1) ? (
709 13
                ($i % 4 === 1) ? DecimalConstants::one() : DecimalConstants::negativeOne()
710 13
                ) : DecimalConstants::zero();
711 13
            },
712
            $scale
713
        );
714
    }
715
716
    /**
717
     * Calculates the cosecant of this with the highest possible accuracy
718
     * Note that accuracy is limited by the accuracy of predefined PI;
719
     *
720
     * @param integer $scale
721
     * @return Decimal
722
     */
723 3
    public function cosec($scale = null)
724
    {
725 3
        $sin = $this->sin($scale + 2);
726 3
        if ($sin->isZero()) {
727
            throw new \DomainException(
728
                "The cosecant of this 'angle' is undefined."
729
            );
730
        }
731
732 3
        return DecimalConstants::one()->div($sin)->round($scale);
733
    }
734
735
    /**
736
     * Calculates the cosine of this method with the highest possible accuracy
737
     * Note that accuracy is limited by the accuracy of predefined PI;
738
     *
739
     * @param integer $scale
740
     * @return Decimal cos($this)
741
     */
742 13
    public function cos($scale = null)
743
    {
744
        // First normalise the number in the [0, 2PI] domain
745 13
        $x = $this->mod(DecimalConstants::PI()->mul(Decimal::fromString("2")));
746
747
        // PI has only 32 significant numbers
748 13
        $scale = ($scale === null) ? 32 : $scale;
749
750 13
        return self::factorialSerie(
751
            $x,
752 13
            DecimalConstants::one(),
753
            function ($i) {
754 13
                return ($i % 2 === 0) ? (
755 13
                    ($i % 4 === 0) ? DecimalConstants::one() : DecimalConstants::negativeOne()
756 13
                ) : DecimalConstants::zero();
757 13
            },
758
            $scale
759
        );
760
    }
761
762
    /**
763
     * Calculates the secant of this with the highest possible accuracy
764
     * Note that accuracy is limited by the accuracy of predefined PI;
765
     *
766
     * @param integer $scale
767
     * @return Decimal
768
     */
769 3
    public function sec($scale = null)
770
    {
771 3
        $cos = $this->cos($scale + 2);
772 3
        if ($cos->isZero()) {
773
            throw new \DomainException(
774
                "The secant of this 'angle' is undefined."
775
            );
776
        }
777
778 3
        return DecimalConstants::one()->div($cos)->round($scale);
779
    }
780
781
    /**
782
     *	Calculates the arcsine of this with the highest possible accuracy
783
     *
784
     * @param integer $scale
785
     * @return Decimal
786
     */
787 5
    public function arcsin($scale = null)
788
    {
789 5
        if($this->comp(DecimalConstants::one(), $scale + 2) === 1 || $this->comp(DecimalConstants::negativeOne(), $scale + 2) === -1) {
790 2
            throw new \DomainException(
791 2
                "The arcsin of this number is undefined."
792
            );
793
        }
794
795 3
        if ($this->round($scale)->isZero()) {
796
            return DecimalConstants::zero;
797
        }
798 3
        if ($this->round($scale)->equals(DecimalConstants::one())) {
799 1
            return DecimalConstants::pi()->div(Decimal::fromInteger(2))->round($scale);
800
        }
801 2
        if ($this->round($scale)->equals(DecimalConstants::negativeOne())) {
802 1
            return DecimalConstants::pi()->div(Decimal::fromInteger(-2))->round($scale);
803
        }
804
805 1
        $scale = ($scale === null) ? 32 : $scale;
806
807 1
        return self::powerSerie(
808
            $this,
809 1
            DecimalConstants::zero(),
810
            $scale
811
        );
812
    }
813
814
    /**
815
     *	Calculates the arccosine of this with the highest possible accuracy
816
     *
817
     * @param integer $scale
818
     * @return Decimal
819
     */
820 5
    public function arccos($scale = null)
821
    {
822 5
        if($this->comp(DecimalConstants::one(), $scale + 2) === 1 || $this->comp(DecimalConstants::negativeOne(), $scale + 2) === -1) {
823 2
            throw new \DomainException(
824 2
                "The arccos of this number is undefined."
825
            );
826
        }
827
828 3
        $piOverTwo = DecimalConstants::pi()->div(Decimal::fromInteger(2), $scale + 2)->round($scale);
829
830 3
        if ($this->round($scale)->isZero()) {
831
            return $piOverTwo;
832
        }
833 3
        if ($this->round($scale)->equals(DecimalConstants::one())) {
834 1
            return DecimalConstants::zero();
835
        }
836 2
        if ($this->round($scale)->equals(DecimalConstants::negativeOne())) {
837 1
            return DecimalConstants::pi()->round($scale);
838
        }
839
840 1
        $scale = ($scale === null) ? 32 : $scale;
841
842 1
        return $piOverTwo->sub(
843 1
            self::powerSerie(
844
                $this,
845 1
                DecimalConstants::zero(),
846
                $scale
847
            )
848 1
        )->round($scale);
849
    }
850
851
    /**
852
     *	Calculates the arctangente of this with the highest possible accuracy
853
     *
854
     * @param integer $scale
855
     * @return Decimal
856
     */
857 3
    public function arctan($scale = null)
858
    {
859 3
        $piOverFour = DecimalConstants::pi()->div(Decimal::fromInteger(4), $scale + 2)->round($scale);
860
861 3
        if ($this->round($scale)->isZero()) {
862 1
            return DecimalConstants::zero();
863
        }
864 2
        if ($this->round($scale)->equals(DecimalConstants::one())) {
865
            return $piOverFour;
866
        }
867 2
        if ($this->round($scale)->equals(DecimalConstants::negativeOne())) {
868 1
            return DecimalConstants::negativeOne()->mul($piOverFour);
869
        }
870
871 1
        $scale = ($scale === null) ? 32 : $scale;
872
873 1
        return self::simplePowerSerie(
874
            $this,
875 1
            DecimalConstants::zero(),
876 1
            $scale + 2
877 1
        )->round($scale);
878
    }
879
880
    /**
881
     * Calculates the arccotangente of this with the highest possible accuracy
882
     *
883
     * @param integer $scale
884
     * @return Decimal
885
     */
886 3
    public function arccot($scale = null) {
887 3
        $scale = ($scale === null) ? 32 : $scale;
888
889 3
        $piOverTwo = DecimalConstants::pi()->div(Decimal::fromInteger(2), $scale + 2);
890 3
        if ($this->round($scale)->isZero()) {
891 1
            return $piOverTwo->round($scale);
892
        }
893
894 2
        $piOverFour = DecimalConstants::pi()->div(Decimal::fromInteger(4), $scale + 2);
895 2
        if ($this->round($scale)->equals(DecimalConstants::one())) {
896
            return $piOverFour->round($scale);
897
        }
898 2
        if ($this->round($scale)->equals(DecimalConstants::negativeOne())) {
899 1
            return DecimalConstants::negativeOne()->mul($piOverFour, $scale + 2)->round($scale);
900
        }
901
902 1
        return $piOverTwo->sub(
903 1
            self::simplePowerSerie(
904
                $this,
905 1
                DecimalConstants::zero(),
906 1
                $scale + 2
907
            )
908 1
        )->round($scale);
909
    }
910
911
    /**
912
     * Calculates the arcsecant of this with the highest possible accuracy
913
     *
914
     * @param integer $scale
915
     * @return Decimal
916
     */
917 5
    public function arcsec($scale = null) {
918 5
        if($this->comp(DecimalConstants::one(), $scale + 2) === -1 && $this->comp(DecimalConstants::negativeOne(), $scale + 2) === 1) {
919 1
            throw new \DomainException(
920 1
                "The arcsecant of this number is undefined."
921
            );
922
        }
923
924 4
        $piOverTwo = DecimalConstants::pi()->div(Decimal::fromInteger(2), $scale + 2)->round($scale);
925
926 4
        if ($this->round($scale)->equals(DecimalConstants::one())) {
927 1
            return DecimalConstants::zero();
928
        }
929 3
        if ($this->round($scale)->equals(DecimalConstants::negativeOne())) {
930 1
            return DecimalConstants::pi()->round($scale);
931
        }
932
933 2
        $scale = ($scale === null) ? 32 : $scale;
934
935 2
        return $piOverTwo->sub(
936 2
            self::powerSerie(
937 2
                DecimalConstants::one()->div($this, $scale + 2),
938 2
                DecimalConstants::zero(),
939 2
                $scale + 2
940
            )
941 2
        )->round($scale);
942
    }
943
944
    /**
945
     * Calculates the arccosecant of this with the highest possible accuracy
946
     *
947
     * @param integer $scale
948
     * @return Decimal
949
     */
950 5
    public function arccsc($scale = null) {
951 5
        if($this->comp(DecimalConstants::one(), $scale + 2) === -1 && $this->comp(DecimalConstants::negativeOne(), $scale + 2) === 1) {
952 1
            throw new \DomainException(
953 1
                "The arccosecant of this number is undefined."
954
            );
955
        }
956
957 4
        $scale = ($scale === null) ? 32 : $scale;
958
959 4
        if ($this->round($scale)->equals(DecimalConstants::one())) {
960 1
            return DecimalConstants::pi()->div(Decimal::fromInteger(2), $scale + 2)->round($scale);
961
        }
962 3
        if ($this->round($scale)->equals(DecimalConstants::negativeOne())) {
963 1
            return DecimalConstants::pi()->div(Decimal::fromInteger(-2), $scale + 2)->round($scale);
964
        }
965
966 2
        return self::powerSerie(
967 2
            DecimalConstants::one()->div($this, $scale + 2),
968 2
            DecimalConstants::zero(),
969 2
            $scale + 2
970 2
        )->round($scale);
971
    }
972
973
    /**
974
     * Returns exp($this), said in other words: e^$this .
975
     *
976
     * @param integer $scale
977
     * @return Decimal
978
     */
979 11
    public function exp($scale = null)
980
    {
981 11
        if ($this->isZero()) {
982 3
            return DecimalConstants::one();
983
        }
984
985 8
        $scale = ($scale === null) ? max(
986
            $this->scale,
987
            (int)($this->isNegative() ? self::innerLog10($this->value, $this->scale, 0) : 16)
988 8
        ) : $scale;
989
990 8
        return self::factorialSerie(
991
            $this, DecimalConstants::one(), function ($i) { return DecimalConstants::one(); }, $scale
0 ignored issues
show
Unused Code introduced by
The parameter $i is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
992
        );
993
    }
994
995
    /**
996
     * Internal method used to compute sin, cos and exp
997
     *
998
     * @param Decimal $x
999
     * @param Decimal $firstTerm
1000
     * @param callable $generalTerm
1001
     * @param $scale
1002
     * @return Decimal
1003
     */
1004 28
    private static function factorialSerie (Decimal $x, Decimal $firstTerm, callable $generalTerm, $scale)
1005
    {
1006 28
        $approx = $firstTerm;
1007 28
        $change = InfiniteDecimal::getPositiveInfinite();
1008
1009 28
        $faculty = DecimalConstants::One();    // Calculates the faculty under the sign
1010 28
        $xPowerN = DecimalConstants::One();    // Calculates x^n
1011
1012 28
        for ($i = 1; !$change->floor($scale+1)->isZero(); $i++) {
1013
            // update x^n and n! for this walkthrough
1014 28
            $xPowerN = $xPowerN->mul($x);
1015 28
            $faculty = $faculty->mul(Decimal::fromInteger($i));
1016
1017 28
            $multiplier = $generalTerm($i);
1018
1019 28
            if (!$multiplier->isZero()) {
1020 28
                $change = $multiplier->mul($xPowerN, $scale + 2)->div($faculty, $scale + 2);
1021 28
                $approx = $approx->add($change, $scale + 2);
1022
            }
1023
        }
1024
1025 28
        return $approx->round($scale);
1026
    }
1027
1028
1029
    /**
1030
     * Internal method used to compute arcsine and arcosine
1031
     *
1032
     * @param Decimal $x
1033
     * @param Decimal $firstTerm
1034
     * @param $scale
1035
     * @return Decimal
1036
     */
1037 6
    private static function powerSerie (Decimal $x, Decimal $firstTerm, $scale)
1038
    {
1039 6
        $approx = $firstTerm;
1040 6
        $change = InfiniteDecimal::getPositiveInfinite();
1041
1042 6
        $xPowerN = DecimalConstants::One();     // Calculates x^n
1043 6
        $factorN = DecimalConstants::One();      // Calculates a_n
0 ignored issues
show
Unused Code introduced by
$factorN is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1044
1045 6
        $numerator = DecimalConstants::one();
1046 6
        $denominator = DecimalConstants::one();
1047
1048 6
        for ($i = 1; !$change->floor($scale + 2)->isZero(); $i++) {
1049 6
            $xPowerN = $xPowerN->mul($x);
1050
1051 6
            if ($i % 2 == 0) {
1052 6
                $factorN = DecimalConstants::zero();
1053 6
            } elseif ($i == 1) {
1054 6
                $factorN = DecimalConstants::one();
1055
            } else {
1056 6
                $incrementNum = Decimal::fromInteger($i - 2);
1057 6
                $numerator = $numerator->mul($incrementNum, $scale +2);
1058
1059 6
                $incrementDen = Decimal::fromInteger($i - 1);
1060 6
                $increment = Decimal::fromInteger($i);
1061
                $denominator = $denominator
1062 6
                    ->div($incrementNum, $scale +2)
1063 6
                    ->mul($incrementDen, $scale +2)
1064 6
                    ->mul($increment, $scale +2);
1065
1066 6
                $factorN = $numerator->div($denominator, $scale + 2);
1067
            }
1068
1069 6
            if (!$factorN->isZero()) {
1070 6
                $change = $factorN->mul($xPowerN, $scale + 2);
1071 6
                $approx = $approx->add($change, $scale + 2);
1072
            }
1073
        }
1074
1075 6
        return $approx->round($scale);
1076
    }
1077
1078
    /**
1079
     * Internal method used to compute arctan and arccotan
1080
     *
1081
     * @param Decimal $x
1082
     * @param Decimal $firstTerm
1083
     * @param $scale
1084
     * @return Decimal
1085
     */
1086 2
    private static function simplePowerSerie (Decimal $x, Decimal $firstTerm, $scale)
1087
    {
1088 2
        $approx = $firstTerm;
1089 2
        $change = InfiniteDecimal::getPositiveInfinite();
1090
1091 2
        $xPowerN = DecimalConstants::One();     // Calculates x^n
1092 2
        $sign = DecimalConstants::One();      // Calculates a_n
0 ignored issues
show
Unused Code introduced by
$sign is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1093
1094 2
        for ($i = 1; !$change->floor($scale + 2)->isZero(); $i++) {
1095 2
            $xPowerN = $xPowerN->mul($x);
1096
1097 2
            if ($i % 2 === 0) {
1098 2
                $factorN = DecimalConstants::zero();
1099
            } else {
1100 2
                 if ($i % 4 === 1) {
1101 2
                     $factorN = DecimalConstants::one()->div(Decimal::fromInteger($i), $scale + 2);
1102
                 } else {
1103 2
                     $factorN = DecimalConstants::negativeOne()->div(Decimal::fromInteger($i), $scale + 2);
1104
                 }
1105
            }
1106
1107 2
            if (!$factorN->isZero()) {
1108 2
                $change = $factorN->mul($xPowerN, $scale + 2);
1109 2
                $approx = $approx->add($change, $scale + 2);
1110
            }
1111
        }
1112
1113 2
        return $approx->round($scale);
1114
    }
1115
1116
    /**
1117
     * Calculates the tangent of this method with the highest possible accuracy
1118
     * Note that accuracy is limited by the accuracy of predefined PI;
1119
     *
1120
     * @param integer $scale
1121
     * @return Decimal tan($this)
1122
     */
1123 4
    public function tan($scale = null) {
1124 4
	    $cos = $this->cos($scale + 2);
1125 4
	    if ($cos->isZero()) {
1126 1
	        throw new \DomainException(
1127 1
	            "The tangent of this 'angle' is undefined."
1128
	        );
1129
	    }
1130
1131 3
	    return $this->sin($scale + 2)->div($cos)->round($scale);
1132
    }
1133
1134
    /**
1135
     * Calculates the cotangent of this method with the highest possible accuracy
1136
     * Note that accuracy is limited by the accuracy of predefined PI;
1137
     *
1138
     * @param integer $scale
1139
     * @return Decimal cotan($this)
1140
     */
1141 4
    public function cotan($scale = null)
1142
    {
1143 4
	    $sin = $this->sin($scale + 2);
1144 4
	    if ($sin->isZero()) {
1145 1
	        throw new \DomainException(
1146 1
	            "The cotangent of this 'angle' is undefined."
1147
	        );
1148
	    }
1149
1150 3
	    return $this->cos($scale + 2)->div($sin)->round($scale);
1151
    }
1152
1153
    /**
1154
     * Indicates if the passed parameter has the same sign as the method's bound object.
1155
     *
1156
     * @param Decimal $b
1157
     * @return bool
1158
     */
1159 3
    public function hasSameSign(Decimal $b)
1160
    {
1161 3
        return $this->isPositive() && $b->isPositive() || $this->isNegative() && $b->isNegative();
1162
    }
1163
1164
    /**
1165
     * Return value as a float
1166
     *
1167
     * @return float
1168
     */
1169 1
    public function asFloat()
1170
    {
1171 1
        return floatval($this->value);
1172
    }
1173
1174
    /**
1175
     * Return value as a integer
1176
     *
1177
     * @return float
1178
     */
1179 1
    public function asInteger()
1180
    {
1181 1
        return intval($this->value);
1182
    }
1183
1184
    /**
1185
     * Return the inner representation of the class
1186
     * use with caution
1187
     *
1188
     * @return number
1189
     */
1190 13
    public function innerValue()
1191
    {
1192 13
        return $this->value;
1193
    }
1194
1195
    /**
1196
     * @return string
1197
     */
1198 58
    public function __toString()
1199
    {
1200 58
        return $this->value;
1201
    }
1202
1203
    /**
1204
     * "Rounds" the decimal string to have at most $scale digits after the point
1205
     *
1206
     * @param  string  $value
1207
     * @param  integer $scale
1208
     * @return string
1209
     */
1210 145
    private static function innerRound($value, $scale = 0)
1211
    {
1212 145
        $rounded = bcadd($value, '0', $scale);
1213
1214 145
        $diffDigit = bcsub($value, $rounded, $scale+1);
1215 145
        $diffDigit = (int)$diffDigit[strlen($diffDigit)-1];
1216
1217 145
        if ($diffDigit >= 5) {
1218 66
            if ($diffDigit >= 5 && $value[0] !== '-') {
1219 62
                $rounded = bcadd($rounded, bcpow('10', -$scale, $scale), $scale);
1220
            } else {
1221 29
                $rounded = bcsub($rounded, bcpow('10', -$scale, $scale), $scale);
1222
            }
1223
        }
1224
1225 145
        return $rounded;
1226
    }
1227
1228
    /**
1229
     * Calculates the logarithm (in base 10) of $value
1230
     *
1231
     * @param  string  $value     The number we want to calculate its logarithm (only positive numbers)
1232
     * @param  integer $in_scale  Expected scale used by $value (only positive numbers)
1233
     * @param  integer $out_scale Scale used by the return value (only positive numbers)
1234
     * @return string
1235
     */
1236 22
    private static function innerLog10($value, $in_scale, $out_scale)
1237
    {
1238 22
        $value_len = strlen($value);
1239
1240 22
        $cmp = bccomp($value, '1', $in_scale);
1241
1242
        switch ($cmp) {
1243 22
            case 1:
1244 9
                $value_log10_approx = $value_len - ($in_scale > 0 ? ($in_scale+2) : 1);
1245
1246 9
                return bcadd(
1247
                    $value_log10_approx,
1248
                    log10(bcdiv(
1249
                        $value,
1250 9
                        bcpow('10', $value_log10_approx),
1251
                        min($value_len, $out_scale)
1252
                    )),
1253
                    $out_scale
1254
                );
1255 16
            case -1:
1256 13
                preg_match('/^0*\.(0*)[1-9][0-9]*$/', $value, $captures);
1257 13
                $value_log10_approx = -strlen($captures[1])-1;
1258
1259 13
                return bcadd(
1260
                    $value_log10_approx,
1261
                    log10(bcmul(
1262
                        $value,
1263 13
                        bcpow('10', -$value_log10_approx),
1264 13
                        $in_scale + $value_log10_approx
1265
                    )),
1266
                    $out_scale
1267
                );
1268
            default: // case 0:
1269 9
                return '0';
1270
        }
1271
    }
1272
1273
    /**
1274
     * Returns $base^$exponent
1275
     *
1276
     * @param  string $base
1277
     * @param  string $exponent   0 < $exponent < 1
1278
     * @param  integer $exp_scale Number of $exponent's significative digits
1279
     * @param  integer $out_scale Number of significative digits that we want to compute
1280
     * @return string
1281
     */
1282 3
    private static function innerPowWithLittleExponent($base, $exponent, $exp_scale, $out_scale)
1283
    {
1284 3
        $inner_scale = ceil($exp_scale*log(10)/log(2))+1;
1285
1286 3
        $result_a = '1';
1287 3
        $result_b = '0';
1288
1289 3
        $actual_index = 0;
1290 3
        $exponent_remaining = $exponent;
1291
1292 3
        while (bccomp($result_a, $result_b, $out_scale) !== 0 && bccomp($exponent_remaining, '0', $inner_scale) !== 0) {
1293 3
            $result_b = $result_a;
1294 3
            $index_info = self::computeSquareIndex($exponent_remaining, $actual_index, $exp_scale, $inner_scale);
1295 3
            $exponent_remaining = $index_info[1];
1296 3
            $result_a = bcmul(
1297
                $result_a,
1298 3
                self::compute2NRoot($base, $index_info[0], 2*($out_scale+1)),
1299 3
                2*($out_scale+1)
1300
            );
1301
        }
1302
1303 3
        return self::innerRound($result_a, $out_scale);
1304
    }
1305
1306
    /**
1307
     * Auxiliar method. It helps us to decompose the exponent into many summands.
1308
     *
1309
     * @param  string  $exponent_remaining
1310
     * @param  integer $actual_index
1311
     * @param  integer $exp_scale           Number of $exponent's significative digits
1312
     * @param  integer $inner_scale         ceil($exp_scale*log(10)/log(2))+1;
1313
     * @return string
1314
     */
1315 3
    private static function computeSquareIndex($exponent_remaining, $actual_index, $exp_scale, $inner_scale)
1316
    {
1317 3
        $actual_rt = bcpow('0.5', $actual_index, $exp_scale);
1318 3
        $r = bcsub($exponent_remaining, $actual_rt, $inner_scale);
1319
1320 3
        while (bccomp($r, 0, $exp_scale) === -1) {
1321 3
            ++$actual_index;
1322 3
            $actual_rt = bcmul('0.5', $actual_rt, $inner_scale);
1323 3
            $r = bcsub($exponent_remaining, $actual_rt, $inner_scale);
1324
        }
1325
1326 3
        return [$actual_index, $r];
1327
    }
1328
1329
    /**
1330
     * Auxiliar method. Computes $base^((1/2)^$index)
1331
     *
1332
     * @param  string  $base
1333
     * @param  integer $index
1334
     * @param  integer $out_scale
1335
     * @return string
1336
     */
1337 3
    private static function compute2NRoot($base, $index, $out_scale)
1338
    {
1339 3
        $result = $base;
1340
1341 3
        for ($i=0; $i<$index; $i++) {
1342 3
            $result = bcsqrt($result, ($out_scale+1)*($index-$i)+1);
1343
        }
1344
1345 3
        return self::innerRound($result, $out_scale);
1346
    }
1347
1348
    /**
1349
     * Validates basic constructor's arguments
1350
     * @param  mixed    $value
1351
     * @param  integer  $scale
1352
     */
1353 183
    protected static function paramsValidation($value, $scale)
1354
    {
1355 183
        if ($value === null) {
1356 1
            throw new \InvalidArgumentException('$value must be a non null number');
1357
        }
1358
1359 182
        if ($scale !== null && (!is_int($scale) || $scale < 0)) {
1360 3
            throw new \InvalidArgumentException('$scale must be a positive integer');
1361
        }
1362 180
    }
1363
1364
    /**
1365
     * @return string
1366
     */
1367 126
    private static function normalizeSign($sign)
1368
    {
1369 126
        if ($sign==='+') {
1370 4
            return '';
1371
        }
1372
1373 126
        return $sign;
1374
    }
1375
1376 1
    private static function removeTrailingZeros($strValue, &$scale)
1377
    {
1378 1
        preg_match('/^[+\-]?[0-9]+(\.([0-9]*[1-9])?(0+)?)?$/', $strValue, $captures);
1379
1380 1
        if (count($captures) === 4) {
1381 1
            $toRemove = strlen($captures[3]);
1382 1
            $scale = strlen($captures[2]);
1383 1
            $strValue = substr($strValue, 0, strlen($strValue)-$toRemove-($scale===0 ? 1 : 0));
1384
        }
1385
1386 1
        return $strValue;
1387
    }
1388
1389
    /**
1390
     * Counts the number of significative digits of $val.
1391
     * Assumes a consistent internal state (without zeros at the end or the start).
1392
     *
1393
     * @param  Decimal $val
1394
     * @param  Decimal $abs $val->abs()
1395
     * @return integer
1396
     */
1397 19
    private static function countSignificativeDigits(Decimal $val, Decimal $abs)
1398
    {
1399 19
        return strlen($val->value) - (
1400 19
            ($abs->comp(DecimalConstants::One()) === -1) ? 2 : max($val->scale, 1)
1401 19
        ) - ($val->isNegative() ? 1 : 0);
1402
    }
1403
}
1404