Completed
Pull Request — master (#66)
by
unknown
14:03
created

Decimal::innerPowWithLittleExponent()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 16
cts 16
cp 1
rs 9.472
c 0
b 0
f 0
cc 3
nc 2
nop 4
crap 3
1
<?php
2
declare(strict_types=1);
3
4
namespace Litipk\BigNumbers;
5
6
use Litipk\BigNumbers\Errors\InfiniteInputError;
7
use Litipk\BigNumbers\Errors\NaNInputError;
8
use Litipk\BigNumbers\Errors\NotImplementedError;
9
10
/**
11
 * Immutable object that represents a rational number
12
 *
13
 * @author Andreu Correa Casablanca <[email protected]>
14
 */
15
class Decimal
16
{
17
    const DEFAULT_SCALE = 16;
18
    const CLASSIC_DECIMAL_NUMBER_REGEXP = '/^([+\-]?)0*(([1-9]\d*|\d)(\.\d+)?)$/';
19
    const EXP_NOTATION_NUMBER_REGEXP = '/^ (?P<sign> [+\-]?) 0*(?P<mantissa> \d(?P<decimals> \.\d+)?) [eE] (?P<expSign> [+\-]?)(?P<exp> \d+)$/x';
20
    const EXP_NUM_GROUPS_NUMBER_REGEXP = '/^ (?P<int> \d*) (?: \. (?P<dec> \d+) ) E (?P<sign>[\+\-]) (?P<exp>\d+) $/x';
21
22
    /**
23
     * Internal numeric value
24
     * @var string
25
     */
26
    protected $value;
27
28
    /**
29
     * Number of digits behind the point
30
     * @var integer
31
     */
32
    private $scale;
33
34
    /**
35
     * Decimal constructor.
36
     *
37
     * @param string $value
38
     * @param int $scale
39
     */
40 171
    private function __construct(string $value, int $scale)
41
    {
42 171
        $this->value = $value;
43 171
        $this->scale = $scale;
44 171
    }
45
46
    private function __clone()
47
    {
48
    }
49
50
    /**
51
     * Decimal "constructor".
52
     *
53
     * @param mixed $value
54
     * @param int $scale
55
     *
56
     * @return Decimal
57
     */
58 9
    public static function create($value, int $scale = null): Decimal
59
    {
60 9
        if (\is_int($value)) {
61 4
            return self::fromInteger($value);
62
        }
63
64 8
        if (\is_float($value)) {
65 4
            return self::fromFloat($value, $scale);
66
        }
67
68 4
        if (\is_string($value)) {
69 2
            return self::fromString($value, $scale);
70
        }
71
72 2
        if ($value instanceof self) {
73 1
            return self::fromDecimal($value, $scale);
74
        }
75
76 1
        throw new \TypeError(
77
            'Expected (int, float, string, Decimal), but received ' .
0 ignored issues
show
Unused Code introduced by
The call to TypeError::__construct() has too many arguments starting with 'Expected (int, float, s...ue) : \gettype($value)).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
78 1
            (\is_object($value) ? \get_class($value) : \gettype($value))
79
        );
80
    }
81
82
    /**
83
     * @param int $intValue
84
     *
85
     * @return Decimal
86
     */
87 98
    public static function fromInteger(int $intValue): Decimal
88
    {
89 98
        self::paramsValidation($intValue, null);
90
91 98
        return new static((string)$intValue, 0);
92
    }
93
94
    /**
95
     * @param float $fltValue
96
     * @param int $scale
97
     *
98
     * @return Decimal
99
     */
100 49
    public static function fromFloat(float $fltValue, int $scale = null): Decimal
101
    {
102 49
        self::paramsValidation($fltValue, $scale);
103
104 49
        if (\is_infinite($fltValue)) {
105
            throw new InfiniteInputError('fltValue must be a finite number');
106
        }
107
108 49
        if (\is_nan($fltValue)) {
109 1
            throw new NaNInputError("fltValue can't be NaN");
110
        }
111
112 48
        $strValue = (string)$fltValue;
113 48
        $hasPoint = (false !== \strpos($strValue, '.'));
114
115 48
        if (\preg_match(self::EXP_NUM_GROUPS_NUMBER_REGEXP, $strValue, $capture)) {
116 6
            if (null === $scale) {
117 3
                $scale = ('-' === $capture['sign'])
118 2
                    ? $capture['exp'] + \strlen($capture['dec'])
119 3
                    : self::DEFAULT_SCALE;
120
            }
121 6
            $strValue = \number_format($fltValue, $scale, '.', '');
122
        } else {
123
            $naturalScale = (
124 42
                \strlen((string)\fmod($fltValue, 1.0)) - 2 - (($fltValue < 0) ? 1 : 0) + (!$hasPoint ? 1 : 0)
125
            );
126
127 42
            if (null === $scale) {
128 40
                $scale = $naturalScale;
129
            } else {
130 2
                $strValue .= ($hasPoint ? '' : '.') . \str_pad('', $scale - $naturalScale, '0');
131
            }
132
        }
133
134 48
        return new static($strValue, $scale);
135
    }
136
137
    /**
138
     * @param string $strValue
139
     * @param integer $scale
140
     *
141
     * @return Decimal
142
     */
143 133
    public static function fromString(string $strValue, int $scale = null): Decimal
144
    {
145 133
        self::paramsValidation($strValue, $scale);
146
147 132
        if (\preg_match(self::CLASSIC_DECIMAL_NUMBER_REGEXP, $strValue, $captures) === 1) {
148
149
            // Now it's time to strip leading zeros in order to normalize inner values
150 127
            $value = self::normalizeSign($captures[1]) . $captures[2];
151 127
            $min_scale = isset($captures[4]) ? \max(0, \strlen($captures[4]) - 1) : 0;
152
153 8
        } elseif (\preg_match(self::EXP_NOTATION_NUMBER_REGEXP, $strValue, $captures) === 1) {
154 7
            list($min_scale, $value) = self::fromExpNotationString(
155 7
                $captures['sign'],
156 7
                $captures['mantissa'],
157 7
                \strlen($captures['mantissa']) - 1,
158 7
                $captures['expSign'],
159 7
                (int)$captures['exp'],
160 7
                $scale
161
            );
162
        } else {
163 1
            throw new NaNInputError('strValue must be a number');
164
        }
165
166 131
        $scale = $scale ?? $min_scale;
167 131
        if ($scale < $min_scale) {
168 70
            $value = self::innerRound($value, $scale);
169 128
        } elseif ($min_scale < $scale) {
170 17
            $hasPoint = (false !== \strpos($value, '.'));
171 17
            $value .= ($hasPoint ? '' : '.') . \str_pad('', $scale - $min_scale, '0');
172
        }
173
174 131
        return new static($value, $scale);
175
    }
176
177
    /**
178
     * Constructs a new Decimal object based on a previous one,
179
     * but changing it's $scale property.
180
     *
181
     * @param Decimal $decValue
182
     * @param null|int $scale
183
     *
184
     * @return Decimal
185
     */
186 3
    public static function fromDecimal(Decimal $decValue, int $scale = null): Decimal
187
    {
188 3
        self::paramsValidation($decValue, $scale);
189
190
        // This block protect us from unnecessary additional instances
191 3
        if ($scale === null || $scale >= $decValue->scale) {
192 3
            return $decValue;
193
        }
194
195 2
        return new static(
196 2
            self::innerRound($decValue->value, $scale),
197 2
            $scale
198
        );
199
    }
200
201
    /**
202
     * Adds two Decimal objects
203
     *
204
     * @param Decimal $b
205
     * @param null|int $scale
206
     *
207
     * @return Decimal
208
     */
209 40
    public function add(Decimal $b, int $scale = null): Decimal
210
    {
211 40
        self::paramsValidation($b, $scale);
212
213 40
        return self::fromString(
214 40
            \bcadd($this->value, $b->value, \max($this->scale, $b->scale)),
215 40
            $scale
216
        );
217
    }
218
219
    /**
220
     * Subtracts two BigNumber objects
221
     *
222
     * @param Decimal $b
223
     * @param integer $scale
224
     *
225
     * @return Decimal
226
     */
227 33
    public function sub(Decimal $b, int $scale = null): Decimal
228
    {
229 33
        self::paramsValidation($b, $scale);
230
231 33
        return self::fromString(
232 33
            \bcsub($this->value, $b->value, \max($this->scale, $b->scale)),
233 33
            $scale
234
        );
235
    }
236
237
    /**
238
     * Multiplies two BigNumber objects
239
     *
240
     * @param Decimal $b
241
     * @param integer $scale
242
     *
243
     * @return Decimal
244
     */
245 48
    public function mul(Decimal $b, int $scale = null): Decimal
246
    {
247 48
        self::paramsValidation($b, $scale);
248
249 47
        if ($b->isZero()) {
250 1
            return DecimalConstants::zero();
251
        }
252
253 47
        return self::fromString(
254 47
            \bcmul($this->value, $b->value, $this->scale + $b->scale),
255 47
            $scale
256
        );
257
    }
258
259
    /**
260
     * Divides the object by $b .
261
     * Warning: div with $scale == 0 is not the same as
262
     *          integer division because it rounds the
263
     *          last digit in order to minimize the error.
264
     *
265
     * @param Decimal $b
266
     * @param integer $scale
267
     *
268
     * @return Decimal
269
     */
270 65
    public function div(Decimal $b, int $scale = null): Decimal
271
    {
272 65
        self::paramsValidation($b, $scale);
273
274 65
        if ($b->isZero()) {
275 1
            throw new \DomainException('Division by zero is not allowed.');
276
        }
277
278 65
        if ($this->isZero()) {
279 1
            return DecimalConstants::zero();
280
        }
281
282 64
        if (null !== $scale) {
283 56
            $divscale = $scale;
284
        } else {
285
            // $divscale is calculated in order to maintain a reasonable precision
286 22
            $this_abs = $this->abs();
287 22
            $b_abs = $b->abs();
288
289
            $log10_result =
290 22
                self::innerLog10($this_abs->value, $this_abs->scale, 1) -
291 22
                self::innerLog10($b_abs->value, $b_abs->scale, 1);
292
293 22
            $divscale = (int)\max(
294 22
                $this->scale + $b->scale,
295 22
                \max(
296 22
                    self::countSignificativeDigits($this, $this_abs),
297 22
                    self::countSignificativeDigits($b, $b_abs)
298 22
                ) - \max(\ceil($log10_result), 0),
299 22
                \ceil(-$log10_result) + 1
300
            );
301
        }
302
303 64
        return self::fromString(
304 64
            \bcdiv($this->value, $b->value, $divscale + 1), $divscale
305
        );
306
    }
307
308
    /**
309
     * Returns the square root of this object
310
     *
311
     * @param integer $scale
312
     *
313
     * @return Decimal
314
     */
315 4
    public function sqrt(int $scale = null): Decimal
316
    {
317 4
        if ($this->isNegative()) {
318 1
            throw new \DomainException(
319 1
                "Decimal can't handle square roots of negative numbers (it's only for real numbers)."
320
            );
321
        }
322
323 3
        if ($this->isZero()) {
324 1
            return DecimalConstants::zero();
325
        }
326
327 3
        $sqrt_scale = ($scale ?? $this->scale);
328
329 3
        return self::fromString(
330 3
            \bcsqrt($this->value, $sqrt_scale + 1),
331 3
            $sqrt_scale
332
        );
333
    }
334
335
    /**
336
     * Powers this value to $b
337
     *
338
     * @param Decimal $b exponent
339
     * @param integer $scale
340
     *
341
     * @return Decimal
342
     */
343 10
    public function pow(Decimal $b, int $scale = null): Decimal
344
    {
345 10
        if ($this->isZero()) {
346 2
            if ($b->isPositive()) {
347 1
                return self::fromDecimal($this, $scale);
348
            }
349
350 1
            throw new \DomainException("zero can't be powered to zero or negative numbers.");
351
        }
352
353 8
        if ($b->isZero()) {
354 1
            return DecimalConstants::one();
355
        }
356
357 7
        if ($b->isNegative()) {
358 2
            return DecimalConstants::one()->div(
359 2
                $this->pow($b->additiveInverse(), max($scale, self::DEFAULT_SCALE)),
360 2
                max($scale, self::DEFAULT_SCALE)
361
            );
362
        }
363
364 7
        if (0 === $b->scale) {
365 4
            $pow_scale = \max($this->scale, $b->scale, $scale ?? 0);
366
367 4
            return self::fromString(
368 4
                \bcpow($this->value, $b->value, $pow_scale + 1),
369 4
                $pow_scale
370
            );
371
        }
372
373 4
        if ($this->isPositive()) {
374 3
            $pow_scale = \max($this->scale, $b->scale, $scale ?? 0);
375
376 3
            $truncated_b = \bcadd($b->value, '0', 0);
377 3
            $remaining_b = \bcsub($b->value, $truncated_b, $b->scale);
378
379 3
            $first_pow_approx = \bcpow($this->value, $truncated_b, $pow_scale + 1);
380 3
            $intermediate_root = self::innerPowWithLittleExponent(
381 3
                $this->value,
382 3
                $remaining_b,
383 3
                $b->scale,
384 3
                $pow_scale + 1
385
            );
386
387 3
            return self::fromString(
388 3
                \bcmul($first_pow_approx, $intermediate_root, $pow_scale + 1),
389 3
                $pow_scale
390
            );
391
        }
392
393
        // $this->isNegative()
394 1
        if (!$b->isInteger()) {
395 1
            throw new NotImplementedError(
396
                "Usually negative numbers can't be powered to non integer numbers. " .
397 1
                'The cases where is possible are not implemented.'
398
            );
399
        }
400
401
        return (\preg_match('/^[+\-]?\d*[02468](\.0+)?$/', $b->value, $captures) === 1)
402
            ? $this->additiveInverse()->pow($b, $scale)                      // $b is an even number
403
            : $this->additiveInverse()->pow($b, $scale)->additiveInverse();  // $b is an odd number
404
    }
405
406
    /**
407
     * Returns the object's logarithm in base 10
408
     *
409
     * @param integer $scale
410
     *
411
     * @return Decimal
412
     */
413 7
    public function log10(int $scale = null): Decimal
414
    {
415 7
        if ($this->isNegative()) {
416 1
            throw new \DomainException(
417 1
                "Decimal can't handle logarithms of negative numbers (it's only for real numbers)."
418
            );
419
        }
420
421 6
        if ($this->isZero()) {
422 1
            throw new \DomainException(
423 1
                "Decimal can't represent infinite numbers."
424
            );
425
        }
426
427 5
        return self::fromString(
428 5
            self::innerLog10($this->value, $this->scale, $scale !== null ? $scale + 1 : $this->scale + 1),
429 5
            $scale
430
        );
431
    }
432
433
    /**
434
     * @param int|null $scale
435
     *
436
     * @return bool
437
     */
438 96
    public function isZero(int $scale = null): bool
439
    {
440 96
        $cmp_scale = $scale ?? $this->scale;
441
442 96
        return (\bccomp(self::innerRound($this->value, $cmp_scale), '0', $cmp_scale) === 0);
443
    }
444
445
    /**
446
     * @return bool
447
     */
448 31
    public function isPositive(): bool
449
    {
450 31
        return ($this->value[0] !== '-' && !$this->isZero());
451
    }
452
453
    /**
454
     * @return bool
455
     */
456 71
    public function isNegative(): bool
457
    {
458 71
        return (strpos($this->value, '-') === 0);
459
    }
460
461
    /**
462
     * @return bool
463
     */
464 3
    public function isInteger(): bool
465
    {
466 3
        return (\preg_match('/^[+\-]?\d+(\.0+)?$/', $this->value, $captures) === 1);
467
    }
468
469
    /**
470
     * Equality comparison between this object and $b
471
     *
472
     * @param Decimal $b
473
     * @param integer $scale
474
     *
475
     * @return boolean
476
     */
477 115
    public function equals(Decimal $b, int $scale = null): bool
478
    {
479 115
        self::paramsValidation($b, $scale);
480
481 115
        if ($this === $b) {
482 2
            return true;
483
        }
484
485 114
        $cmp_scale = $scale ?? \max($this->scale, $b->scale);
486
487
        return (
488 114
            \bccomp(
489 114
                self::innerRound($this->value, $cmp_scale),
490 114
                self::innerRound($b->value, $cmp_scale),
491 114
                $cmp_scale
492 114
            ) === 0
493
        );
494
    }
495
496
    /**
497
     * $this > $b : returns 1 , $this < $b : returns -1 , $this == $b : returns 0
498
     *
499
     * @param Decimal $b
500
     * @param integer $scale
501
     *
502
     * @return integer
503
     */
504 56
    public function comp(Decimal $b, int $scale = null): int
505
    {
506 56
        self::paramsValidation($b, $scale);
507
508 56
        if ($this === $b) {
509 7
            return 0;
510
        }
511
512 55
        $cmp_scale = $scale ?? \max($this->scale, $b->scale);
513
514 55
        return \bccomp(
515 55
            self::innerRound($this->value, $cmp_scale),
516 55
            self::innerRound($b->value, $cmp_scale),
517 55
            $cmp_scale
518
        );
519
    }
520
521
522
    /**
523
     * Returns true if $this > $b, otherwise false
524
     *
525
     * @param Decimal $b
526
     * @param integer $scale
527
     *
528
     * @return bool
529
     */
530 3
    public function isGreaterThan(Decimal $b, int $scale = null): bool
531
    {
532 3
        return $this->comp($b, $scale) === 1;
533
    }
534
535
    /**
536
     * Returns true if $this >= $b
537
     *
538
     * @param Decimal $b
539
     * @param integer $scale
540
     *
541
     * @return bool
542
     */
543 3
    public function isGreaterOrEqualTo(Decimal $b, int $scale = null): bool
544
    {
545 3
        $comparisonResult = $this->comp($b, $scale);
546
547 3
        return $comparisonResult === 1 || $comparisonResult === 0;
548
    }
549
550
    /**
551
     * Returns true if $this < $b, otherwise false
552
     *
553
     * @param Decimal $b
554
     * @param integer $scale
555
     *
556
     * @return bool
557
     */
558 3
    public function isLessThan(Decimal $b, int $scale = null): bool
559
    {
560 3
        return $this->comp($b, $scale) === -1;
561
    }
562
563
    /**
564
     * Returns true if $this <= $b, otherwise false
565
     *
566
     * @param Decimal $b
567
     * @param integer $scale
568
     *
569
     * @return bool
570
     */
571 3
    public function isLessOrEqualTo(Decimal $b, int $scale = null): bool
572
    {
573 3
        $comparisonResult = $this->comp($b, $scale);
574
575 3
        return $comparisonResult === -1 || $comparisonResult === 0;
576
    }
577
578
    /**
579
     * Returns the element's additive inverse.
580
     *
581
     * @return Decimal
582
     */
583 15
    public function additiveInverse(): Decimal
584
    {
585 15
        if ($this->isZero()) {
586 1
            return $this;
587
        }
588
589 14
        if ($this->isNegative()) {
590 12
            $value = \substr($this->value, 1);
591
        } else { // if ($this->isPositive()) {
592 2
            $value = '-' . $this->value;
593
        }
594
595 14
        return new static($value, $this->scale);
596
    }
597
598
599
    /**
600
     * "Rounds" the Decimal to have at most $scale digits after the point
601
     *
602
     * @param integer $scale
603
     *
604
     * @return Decimal
605
     */
606 52
    public function round(int $scale = 0): Decimal
607
    {
608 52
        if ($scale >= $this->scale) {
609 21
            return $this;
610
        }
611
612 51
        return self::fromString(self::innerRound($this->value, $scale));
613
    }
614
615
    /**
616
     * "Ceils" the Decimal to have at most $scale digits after the point
617
     *
618
     * @param integer $scale
619
     *
620
     * @return Decimal
621
     */
622 4
    public function ceil($scale = 0): Decimal
623
    {
624 4
        if ($scale >= $this->scale) {
625 2
            return $this;
626
        }
627
628 3
        if ($this->isNegative()) {
629 1
            return self::fromString(\bcadd($this->value, '0', $scale));
630
        }
631
632 2
        return $this->innerTruncate($scale);
633
    }
634
635
    /**
636
     * @param int $scale
637
     * @param bool $ceil
638
     *
639
     * @return Decimal
640
     */
641 28
    private function innerTruncate(int $scale = 0, bool $ceil = true): Decimal
642
    {
643 28
        $rounded = \bcadd($this->value, '0', $scale);
644
645 28
        $rlen = \strlen($rounded);
646 28
        $tlen = \strlen($this->value);
647
648 28
        $mustTruncate = false;
649 28
        for ($i = $tlen - 1; $i >= $rlen; $i--) {
650 28
            if ((int)$this->value[$i] > 0) {
651 28
                $mustTruncate = true;
652 28
                break;
653
            }
654
        }
655
656 28
        if ($mustTruncate) {
657 28
            $rounded = $ceil
658 2
                ? \bcadd($rounded, \bcpow('10', (string)-$scale, $scale), $scale)
659 28
                : \bcsub($rounded, \bcpow('10', (string)-$scale, $scale), $scale);
660
        }
661
662 28
        return self::fromString($rounded, $scale);
663
    }
664
665
    /**
666
     * "Floors" the Decimal to have at most $scale digits after the point
667
     *
668
     * @param integer $scale
669
     *
670
     * @return Decimal
671
     */
672 47
    public function floor(int $scale = 0): Decimal
673
    {
674 47
        if ($scale >= $this->scale) {
675 38
            return $this;
676
        }
677
678 38
        if ($this->isNegative()) {
679 26
            return $this->innerTruncate($scale, false);
680
        }
681
682 35
        return self::fromString(\bcadd($this->value, '0', $scale));
683
    }
684
685
    /**
686
     * Returns the absolute value (always a positive number)
687
     *
688
     * @return Decimal
689
     */
690 23
    public function abs(): Decimal
691
    {
692 23
        return ($this->isZero() || $this->isPositive())
693 21
            ? $this
694 23
            : $this->additiveInverse();
695
    }
696
697
    /**
698
     * Calculate modulo with a decimal
699
     *
700
     * @param Decimal $d
701
     * @param integer $scale
702
     *
703
     * @return $this % $d
704
     */
705 27
    public function mod(Decimal $d, int $scale = null): Decimal
706
    {
707 27
        $div = $this->div($d, 1)->floor();
708 27
        return $this->sub($div->mul($d), $scale);
709
    }
710
711
    /**
712
     * Calculates the sine of this method with the highest possible accuracy
713
     * Note that accuracy is limited by the accuracy of predefined PI;
714
     *
715
     * @param integer $scale
716
     *
717
     * @return Decimal sin($this)
718
     */
719 13
    public function sin(int $scale = null): Decimal
720
    {
721
        // First normalise the number in the [0, 2PI] domain
722 13
        $x = $this->mod(DecimalConstants::pi()->mul(self::fromString('2')));
723
724
        // PI has only 32 significant numbers
725 13
        $scale = $scale ?? 32;
726
727 13
        return self::factorialSerie(
728 13
            $x,
729 13
            DecimalConstants::zero(),
730 13
            function ($i) {
731 13
                if ($i % 2 === 1) {
732 13
                    return ($i % 4 === 1) ? DecimalConstants::one() : DecimalConstants::negativeOne();
733
                }
734 13
                return DecimalConstants::zero();
735 13
            },
736 13
            $scale
737
        );
738
    }
739
740
    /**
741
     * Calculates the cosecant of this with the highest possible accuracy
742
     * Note that accuracy is limited by the accuracy of predefined PI;
743
     *
744
     * @param integer $scale
745
     *
746
     * @return Decimal
747
     */
748 3
    public function cosec(int $scale = null): Decimal
749
    {
750 3
        $sin = $this->sin($scale + 2);
751 3
        if ($sin->isZero()) {
752
            throw new \DomainException(
753
                "The cosecant of this 'angle' is undefined."
754
            );
755
        }
756
757 3
        return DecimalConstants::one()->div($sin)->round($scale);
758
    }
759
760
    /**
761
     * Calculates the cosine of this method with the highest possible accuracy
762
     * Note that accuracy is limited by the accuracy of predefined PI;
763
     *
764
     * @param integer $scale
765
     *
766
     * @return Decimal cos($this)
767
     */
768 13
    public function cos(int $scale = null): Decimal
769
    {
770
        // First normalise the number in the [0, 2PI] domain
771 13
        $x = $this->mod(DecimalConstants::pi()->mul(self::fromString('2')));
772
773
        // PI has only 32 significant numbers
774 13
        $scale = $scale ?? 32;
775
776 13
        return self::factorialSerie(
777 13
            $x,
778 13
            DecimalConstants::one(),
779 13
            function ($i) {
780 13
                if ($i % 4 === 0) {
781 13
                    return ($i % 2 === 0) ? DecimalConstants::one() : DecimalConstants::zero();
782
                }
783
784 13
                return ($i % 2 === 0) ? DecimalConstants::negativeOne() : DecimalConstants::zero();
785 13
            },
786 13
            $scale
787
        );
788
    }
789
790
    /**
791
     * Calculates the secant of this with the highest possible accuracy
792
     * Note that accuracy is limited by the accuracy of predefined PI;
793
     *
794
     * @param integer $scale
795
     *
796
     * @return Decimal
797
     */
798 3
    public function sec(int $scale = null): Decimal
799
    {
800 3
        $cos = $this->cos($scale + 2);
801 3
        if ($cos->isZero()) {
802
            throw new \DomainException(
803
                "The secant of this 'angle' is undefined."
804
            );
805
        }
806
807 3
        return DecimalConstants::one()->div($cos)->round($scale);
808
    }
809
810
    /**
811
     * Calculates the arcsine of this with the highest possible accuracy
812
     *
813
     * @param integer $scale
814
     *
815
     * @return Decimal
816
     */
817 5
    public function arcsin(int $scale = null): Decimal
818
    {
819 5
        if ($this->comp(DecimalConstants::one(), $scale + 2) === 1 || $this->comp(DecimalConstants::negativeOne(),
820 5
                $scale + 2) === -1) {
821 2
            throw new \DomainException(
822 2
                'The arcsin of this number is undefined.'
823
            );
824
        }
825
826 3
        if ($this->round($scale)->isZero()) {
827
            return DecimalConstants::zero();
828
        }
829 3
        if ($this->round($scale)->equals(DecimalConstants::one())) {
830 1
            return DecimalConstants::pi()->div(self::fromInteger(2))->round($scale);
831
        }
832 2
        if ($this->round($scale)->equals(DecimalConstants::negativeOne())) {
833 1
            return DecimalConstants::pi()->div(self::fromInteger(-2))->round($scale);
834
        }
835
836 1
        $scale = $scale ?? 32;
837
838 1
        return self::powerSerie(
839 1
            $this,
840 1
            DecimalConstants::zero(),
841 1
            $scale
842
        );
843
    }
844
845
    /**
846
     * Calculates the arccosine of this with the highest possible accuracy
847
     *
848
     * @param integer $scale
849
     *
850
     * @return Decimal
851
     */
852 5
    public function arccos(int $scale = null): Decimal
853
    {
854 5
        if ($this->comp(DecimalConstants::one(), $scale + 2) === 1 || $this->comp(DecimalConstants::negativeOne(),
855 5
                $scale + 2) === -1) {
856 2
            throw new \DomainException(
857 2
                'The arccos of this number is undefined.'
858
            );
859
        }
860
861 3
        $piOverTwo = DecimalConstants::pi()->div(self::fromInteger(2), $scale + 2)->round($scale);
862
863 3
        if ($this->round($scale)->isZero()) {
864
            return $piOverTwo;
865
        }
866 3
        if ($this->round($scale)->equals(DecimalConstants::one())) {
867 1
            return DecimalConstants::zero();
868
        }
869 2
        if ($this->round($scale)->equals(DecimalConstants::negativeOne())) {
870 1
            return DecimalConstants::pi()->round($scale);
871
        }
872
873 1
        $scale = $scale ?? 32;
874
875 1
        return $piOverTwo->sub(
876 1
            self::powerSerie(
877 1
                $this,
878 1
                DecimalConstants::zero(),
879 1
                $scale
880
            )
881 1
        )->round($scale);
882
    }
883
884
    /**
885
     * Calculates the arctangente of this with the highest possible accuracy
886
     *
887
     * @param integer $scale
888
     *
889
     * @return Decimal
890
     */
891 3
    public function arctan(int $scale = null): Decimal
892
    {
893 3
        $piOverFour = DecimalConstants::pi()->div(self::fromInteger(4), $scale + 2)->round($scale);
894
895 3
        if ($this->round($scale)->isZero()) {
896 1
            return DecimalConstants::zero();
897
        }
898 2
        if ($this->round($scale)->equals(DecimalConstants::one())) {
899
            return $piOverFour;
900
        }
901 2
        if ($this->round($scale)->equals(DecimalConstants::negativeOne())) {
902 1
            return DecimalConstants::negativeOne()->mul($piOverFour);
903
        }
904
905 1
        $scale = $scale ?? 32;
906
907 1
        return self::simplePowerSerie(
908 1
            $this,
909 1
            DecimalConstants::zero(),
910 1
            $scale + 2
911 1
        )->round($scale);
912
    }
913
914
    /**
915
     * Calculates the arccotangente of this with the highest possible accuracy
916
     *
917
     * @param integer $scale
918
     *
919
     * @return Decimal
920
     */
921 3
    public function arccot(int $scale = null): Decimal
922
    {
923 3
        $scale = $scale ?? 32;
924
925 3
        $piOverTwo = DecimalConstants::pi()->div(self::fromInteger(2), $scale + 2);
926 3
        if ($this->round($scale)->isZero()) {
927 1
            return $piOverTwo->round($scale);
928
        }
929
930 2
        $piOverFour = DecimalConstants::pi()->div(self::fromInteger(4), $scale + 2);
931 2
        if ($this->round($scale)->equals(DecimalConstants::one())) {
932
            return $piOverFour->round($scale);
933
        }
934 2
        if ($this->round($scale)->equals(DecimalConstants::negativeOne())) {
935 1
            return DecimalConstants::negativeOne()->mul($piOverFour, $scale + 2)->round($scale);
936
        }
937
938 1
        return $piOverTwo->sub(
939 1
            self::simplePowerSerie(
940 1
                $this,
941 1
                DecimalConstants::zero(),
942 1
                $scale + 2
943
            )
944 1
        )->round($scale);
945
    }
946
947
    /**
948
     * Calculates the arcsecant of this with the highest possible accuracy
949
     *
950
     * @param integer $scale
951
     *
952
     * @return Decimal
953
     */
954 5
    public function arcsec(int $scale = null): Decimal
955
    {
956 5
        if ($this->comp(DecimalConstants::one(), $scale + 2) === -1 && $this->comp(DecimalConstants::negativeOne(),
957 5
                $scale + 2) === 1) {
958 1
            throw new \DomainException(
959 1
                'The arcsecant of this number is undefined.'
960
            );
961
        }
962
963 4
        $piOverTwo = DecimalConstants::pi()->div(self::fromInteger(2), $scale + 2)->round($scale);
964
965 4
        if ($this->round($scale)->equals(DecimalConstants::one())) {
966 1
            return DecimalConstants::zero();
967
        }
968 3
        if ($this->round($scale)->equals(DecimalConstants::negativeOne())) {
969 1
            return DecimalConstants::pi()->round($scale);
970
        }
971
972 2
        $scale = $scale ?? 32;
973
974 2
        return $piOverTwo->sub(
975 2
            self::powerSerie(
976 2
                DecimalConstants::one()->div($this, $scale + 2),
977 2
                DecimalConstants::zero(),
978 2
                $scale + 2
979
            )
980 2
        )->round($scale);
981
    }
982
983
    /**
984
     * Calculates the arccosecant of this with the highest possible accuracy
985
     *
986
     * @param integer $scale
987
     *
988
     * @return Decimal
989
     */
990 5
    public function arccsc(int $scale = null): Decimal
991
    {
992 5
        if ($this->comp(DecimalConstants::one(), $scale + 2) === -1 && $this->comp(DecimalConstants::negativeOne(),
993 5
                $scale + 2) === 1) {
994 1
            throw new \DomainException(
995 1
                'The arccosecant of this number is undefined.'
996
            );
997
        }
998
999 4
        $scale = $scale ?? 32;
1000
1001 4
        if ($this->round($scale)->equals(DecimalConstants::one())) {
1002 1
            return DecimalConstants::pi()->div(self::fromInteger(2), $scale + 2)->round($scale);
1003
        }
1004 3
        if ($this->round($scale)->equals(DecimalConstants::negativeOne())) {
1005 1
            return DecimalConstants::pi()->div(self::fromInteger(-2), $scale + 2)->round($scale);
1006
        }
1007
1008 2
        return self::powerSerie(
1009 2
            DecimalConstants::one()->div($this, $scale + 2),
1010 2
            DecimalConstants::zero(),
1011 2
            $scale + 2
1012 2
        )->round($scale);
1013
    }
1014
1015
    /**
1016
     * Returns exp($this), said in other words: e^$this .
1017
     *
1018
     * @param integer $scale
1019
     *
1020
     * @return Decimal
1021
     */
1022 11
    public function exp(int $scale = null): Decimal
1023
    {
1024 11
        if ($this->isZero()) {
1025 3
            return DecimalConstants::one();
1026
        }
1027
1028 8
        $scale = $scale ?? \max(
1029
                $this->scale,
1030
                (int)($this->isNegative()
1031
                    ? self::innerLog10($this->value, $this->scale, 0)
1032 8
                    : self::DEFAULT_SCALE)
1033
            );
1034
1035 8
        return self::factorialSerie(
1036 8
            $this,
1037 8
            DecimalConstants::one(),
1038 8
            function () {
1039 8
                return DecimalConstants::one();
1040 8
            },
1041 8
            $scale
1042
        );
1043
    }
1044
1045
    /**
1046
     * Internal method used to compute sin, cos and exp
1047
     *
1048
     * @param Decimal $x
1049
     * @param Decimal $firstTerm
1050
     * @param callable $generalTerm
1051
     * @param $scale
1052
     *
1053
     * @return Decimal
1054
     */
1055 28
    private static function factorialSerie(Decimal $x, Decimal $firstTerm, callable $generalTerm, int $scale): Decimal
1056
    {
1057 28
        $approx = $firstTerm;
1058 28
        $change = DecimalConstants::one();
1059
1060 28
        $faculty = DecimalConstants::one();    // Calculates the faculty under the sign
1061 28
        $xPowerN = DecimalConstants::one();    // Calculates x^n
1062
1063 28
        for ($i = 1; !$change->floor($scale + 1)->isZero(); $i++) {
1064
            // update x^n and n! for this walkthrough
1065 28
            $xPowerN = $xPowerN->mul($x);
1066 28
            $faculty = $faculty->mul(self::fromInteger($i));
1067
1068
            /** @var Decimal $multiplier */
1069 28
            $multiplier = $generalTerm($i);
1070
1071 28
            if (!$multiplier->isZero()) {
1072 28
                $change = $multiplier->mul($xPowerN, $scale + 2)->div($faculty, $scale + 2);
1073 28
                $approx = $approx->add($change, $scale + 2);
1074
            }
1075
        }
1076
1077 28
        return $approx->round($scale);
1078
    }
1079
1080
1081
    /**
1082
     * Internal method used to compute arcsine and arcosine
1083
     *
1084
     * @param Decimal $x
1085
     * @param Decimal $firstTerm
1086
     * @param $scale
1087
     *
1088
     * @return Decimal
1089
     */
1090 6
    private static function powerSerie(Decimal $x, Decimal $firstTerm, int $scale): Decimal
1091
    {
1092 6
        $approx = $firstTerm;
1093 6
        $change = DecimalConstants::one();
1094
1095 6
        $xPowerN = DecimalConstants::one();     // Calculates x^n
1096
1097 6
        $numerator = DecimalConstants::one();
1098 6
        $denominator = DecimalConstants::one();
1099
1100 6
        for ($i = 1; !$change->floor($scale + 2)->isZero(); $i++) {
1101 6
            $xPowerN = $xPowerN->mul($x);
1102
1103 6
            if ($i % 2 === 0) {
1104 6
                $factorN = DecimalConstants::zero();
1105 6
            } elseif ($i === 1) {
1106 6
                $factorN = DecimalConstants::one();
1107
            } else {
1108 6
                $incrementNum = self::fromInteger($i - 2);
1109 6
                $numerator = $numerator->mul($incrementNum, $scale + 2);
1110
1111 6
                $incrementDen = self::fromInteger($i - 1);
1112 6
                $increment = self::fromInteger($i);
1113
                $denominator = $denominator
1114 6
                    ->div($incrementNum, $scale + 2)
1115 6
                    ->mul($incrementDen, $scale + 2)
1116 6
                    ->mul($increment, $scale + 2);
1117
1118 6
                $factorN = $numerator->div($denominator, $scale + 2);
1119
            }
1120
1121 6
            if (!$factorN->isZero()) {
1122 6
                $change = $factorN->mul($xPowerN, $scale + 2);
1123 6
                $approx = $approx->add($change, $scale + 2);
1124
            }
1125
        }
1126
1127 6
        return $approx->round($scale);
1128
    }
1129
1130
    /**
1131
     * Internal method used to compute arctan and arccotan
1132
     *
1133
     * @param Decimal $x
1134
     * @param Decimal $firstTerm
1135
     * @param $scale
1136
     *
1137
     * @return Decimal
1138
     */
1139 2
    private static function simplePowerSerie(Decimal $x, Decimal $firstTerm, int $scale): Decimal
1140
    {
1141 2
        $approx = $firstTerm;
1142 2
        $change = DecimalConstants::one();
1143
1144 2
        $xPowerN = DecimalConstants::one();     // Calculates x^n
1145
1146 2
        for ($i = 1; !$change->floor($scale + 2)->isZero(); $i++) {
1147 2
            $xPowerN = $xPowerN->mul($x);
1148
1149 2
            if ($i % 2 === 0) {
1150 2
                $factorN = DecimalConstants::zero();
1151 2
            } elseif ($i % 4 === 1) {
1152 2
                $factorN = DecimalConstants::one()->div(self::fromInteger($i), $scale + 2);
1153
            } else {
1154 2
                $factorN = DecimalConstants::negativeOne()->div(self::fromInteger($i), $scale + 2);
1155
            }
1156
1157 2
            if (!$factorN->isZero()) {
1158 2
                $change = $factorN->mul($xPowerN, $scale + 2);
1159 2
                $approx = $approx->add($change, $scale + 2);
1160
            }
1161
        }
1162
1163 2
        return $approx->round($scale);
1164
    }
1165
1166
    /**
1167
     * Calculates the tangent of this method with the highest possible accuracy
1168
     * Note that accuracy is limited by the accuracy of predefined PI;
1169
     *
1170
     * @param integer $scale
1171
     *
1172
     * @return Decimal tan($this)
1173
     */
1174 4
    public function tan(int $scale = null): Decimal
1175
    {
1176 4
        $cos = $this->cos($scale + 2);
1177 4
        if ($cos->isZero()) {
1178 1
            throw new \DomainException(
1179 1
                "The tangent of this 'angle' is undefined."
1180
            );
1181
        }
1182
1183 3
        return $this->sin($scale + 2)->div($cos)->round($scale);
1184
    }
1185
1186
    /**
1187
     * Calculates the cotangent of this method with the highest possible accuracy
1188
     * Note that accuracy is limited by the accuracy of predefined PI;
1189
     *
1190
     * @param integer $scale
1191
     *
1192
     * @return Decimal cotan($this)
1193
     */
1194 4
    public function cotan(int $scale = null): Decimal
1195
    {
1196 4
        $sin = $this->sin($scale + 2);
1197 4
        if ($sin->isZero()) {
1198 1
            throw new \DomainException(
1199 1
                "The cotangent of this 'angle' is undefined."
1200
            );
1201
        }
1202
1203 3
        return $this->cos($scale + 2)->div($sin)->round($scale);
1204
    }
1205
1206
    /**
1207
     * Indicates if the passed parameter has the same sign as the method's bound object.
1208
     *
1209
     * @param Decimal $b
1210
     *
1211
     * @return bool
1212
     */
1213
    public function hasSameSign(Decimal $b): bool
1214
    {
1215
        return ($this->isPositive() && $b->isPositive()) || ($this->isNegative() && $b->isNegative());
1216
    }
1217
1218
    /**
1219
     * @return float
1220
     */
1221 5
    public function asFloat(): float
1222
    {
1223 5
        return (float)$this->value;
1224
    }
1225
1226
    /**
1227
     * @return int
1228
     */
1229 1
    public function asInteger(): int
1230
    {
1231 1
        return (int)$this->value;
1232
    }
1233
1234
    /**
1235
     * WARNING: use with caution! Return the inner representation of the class.
1236
     *
1237
     * @return string
1238
     */
1239 11
    public function innerValue(): string
1240
    {
1241 11
        return $this->value;
1242
    }
1243
1244
    /**
1245
     * @return string
1246
     */
1247 58
    public function __toString(): string
1248
    {
1249 58
        return $this->value;
1250
    }
1251
1252
    /**
1253
     * @param string $sign
1254
     * @param string $mantissa
1255
     * @param int $nDecimals
1256
     * @param string $expSign
1257
     * @param int $expVal
1258
     * @param int|null $scale
1259
     *
1260
     * @return array
1261
     */
1262 7
    private static function fromExpNotationString(
1263
        string $sign,
1264
        string $mantissa,
1265
        int $nDecimals,
1266
        string $expSign,
1267
        int $expVal,
1268
        int $scale = null
1269
    ): array {
1270 7
        $mantissaScale = \max($nDecimals, 0);
1271
1272 7
        if (self::normalizeSign($expSign) === '') {
1273 5
            $minScale = \max($mantissaScale - $expVal, 0);
1274 5
            $tmp_multiplier = \bcpow('10', (string)$expVal);
1275
        } else {
1276 2
            $minScale = $mantissaScale + $expVal;
1277 2
            $tmp_multiplier = \bcpow('10', (string)-$expVal, $expVal);
1278
        }
1279
1280
        $value = (
1281 7
            self::normalizeSign($sign) .
1282 7
            \bcmul(
1283 7
                $mantissa,
1284 7
                $tmp_multiplier,
1285 7
                \max($minScale, $scale ?? 0)
1286
            )
1287
        );
1288
1289 7
        return [$minScale, $value];
1290
    }
1291
1292
    /**
1293
     * "Rounds" the decimal string to have at most $scale digits after the point
1294
     *
1295
     * @param string $value
1296
     * @param int $scale
1297
     *
1298
     * @return string
1299
     */
1300 150
    private static function innerRound(string $value, int $scale = 0): string
1301
    {
1302 150
        $rounded = \bcadd($value, '0', $scale);
1303
1304 150
        $diffDigit = \bcsub($value, $rounded, $scale + 1);
1305 150
        $diffDigit = (int)$diffDigit[\strlen($diffDigit) - 1];
1306
1307 150
        if ($diffDigit >= 5) {
1308 68
            $rounded = ($diffDigit >= 5 && $value[0] !== '-')
1309 64
                ? \bcadd($rounded, \bcpow('10', (string)-$scale, $scale), $scale)
1310 68
                : \bcsub($rounded, \bcpow('10', (string)-$scale, $scale), $scale);
1311
        }
1312
1313 150
        return $rounded;
1314
    }
1315
1316
    /**
1317
     * Calculates the logarithm (in base 10) of $value
1318
     *
1319
     * @param string $value The number we want to calculate its logarithm (only positive numbers)
1320
     * @param int $in_scale Expected scale used by $value (only positive numbers)
1321
     * @param int $out_scale Scale used by the return value (only positive numbers)
1322
     *
1323
     * @return string
1324
     */
1325 25
    private static function innerLog10(string $value, int $in_scale, int $out_scale): string
1326
    {
1327 25
        $value_len = \strlen($value);
1328
1329 25
        $cmp = \bccomp($value, '1', $in_scale);
1330
1331
        switch ($cmp) {
1332 25
            case 1:
1333 12
                $value_log10_approx = $value_len - ($in_scale > 0 ? ($in_scale + 2) : 1);
1334 12
                $value_log10_approx = max(0, $value_log10_approx);
1335
1336 12
                return \bcadd(
1337 12
                    (string)$value_log10_approx,
1338 12
                    (string)\log10((float)\bcdiv(
1339 12
                        $value,
1340 12
                        \bcpow('10', (string)$value_log10_approx),
1341 12
                        \min($value_len, $out_scale)
1342
                    )),
1343 12
                    $out_scale
1344
                );
1345
1346 15
            case -1:
1347 14
                \preg_match('/^0*\.(0*)[1-9]\d*$/', $value, $captures);
1348 14
                $value_log10_approx = -\strlen($captures[1]) - 1;
1349
1350 14
                return \bcadd(
1351 14
                    (string)$value_log10_approx,
1352 14
                    (string)\log10((float)\bcmul(
1353 14
                        $value,
1354 14
                        \bcpow('10', (string)-$value_log10_approx),
1355 14
                        $in_scale + $value_log10_approx
1356
                    )),
1357 14
                    $out_scale
1358
                );
1359
1360
            default: // case 0:
1361 7
                return '0';
1362
        }
1363
    }
1364
1365
    /**
1366
     * Returns $base^$exponent
1367
     *
1368
     * @param string $base
1369
     * @param string $exponent 0 < $exponent < 1
1370
     * @param int $exp_scale Number of $exponent's significative digits
1371
     * @param int $out_scale Number of significative digits that we want to compute
1372
     *
1373
     * @return string
1374
     */
1375 3
    private static function innerPowWithLittleExponent(
1376
        string $base,
1377
        string $exponent,
1378
        int $exp_scale,
1379
        int $out_scale
1380
    ): string {
1381 3
        $inner_scale = (int)\ceil($exp_scale * \log(10) / \log(2)) + 1;
1382
1383 3
        $result_a = '1';
1384 3
        $result_b = '0';
1385
1386 3
        $actual_index = 0;
1387 3
        $exponent_remaining = $exponent;
1388
1389 3
        while (\bccomp($result_a, $result_b, $out_scale) !== 0
1390 3
            && \bccomp($exponent_remaining, '0', $inner_scale) !== 0) {
1391 3
            $result_b = $result_a;
1392 3
            $index_info = self::computeSquareIndex($exponent_remaining, $actual_index, $exp_scale, $inner_scale);
1393 3
            $exponent_remaining = $index_info[1];
1394 3
            $result_a = \bcmul(
1395 3
                $result_a,
1396 3
                self::compute2NRoot($base, $index_info[0], 2 * ($out_scale + 1)),
1397 3
                2 * ($out_scale + 1)
1398
            );
1399
        }
1400
1401 3
        return self::innerRound($result_a, $out_scale);
1402
    }
1403
1404
    /**
1405
     * Auxiliar method. It helps us to decompose the exponent into many summands.
1406
     *
1407
     * @param string $exponent_remaining
1408
     * @param int $actual_index
1409
     * @param int $exp_scale Number of $exponent's significative digits
1410
     * @param int $inner_scale ceil($exp_scale*log(10)/log(2))+1;
1411
     *
1412
     * @return array
1413
     */
1414 3
    private static function computeSquareIndex(
1415
        string $exponent_remaining,
1416
        int $actual_index,
1417
        int $exp_scale,
1418
        int $inner_scale
1419
    ): array {
1420 3
        $actual_rt = \bcpow('0.5', (string)$actual_index, $exp_scale);
1421 3
        $r = \bcsub($exponent_remaining, $actual_rt, $inner_scale);
1422
1423 3
        while (\bccomp($r, '0', $exp_scale) === -1) {
1424 3
            ++$actual_index;
1425 3
            $actual_rt = \bcmul('0.5', $actual_rt, $inner_scale);
1426 3
            $r = \bcsub($exponent_remaining, $actual_rt, $inner_scale);
1427
        }
1428
1429 3
        return [$actual_index, $r];
1430
    }
1431
1432
    /**
1433
     * Auxiliar method. Computes $base^((1/2)^$index)
1434
     *
1435
     * @param  string $base
1436
     * @param  integer $index
1437
     * @param  integer $out_scale
1438
     *
1439
     * @return string
1440
     */
1441 3
    private static function compute2NRoot(string $base, int $index, int $out_scale): string
1442
    {
1443 3
        $result = $base;
1444
1445 3
        for ($i = 0; $i < $index; $i++) {
1446 3
            $result = \bcsqrt($result, ($out_scale + 1) * ($index - $i) + 1);
1447
        }
1448
1449 3
        return self::innerRound($result, $out_scale);
1450
    }
1451
1452
    /**
1453
     * Validates basic constructor's arguments
1454
     *
1455
     * @param mixed $value
1456
     * @param null|int $scale
1457
     */
1458 174
    protected static function paramsValidation($value, int $scale = null)
1459
    {
1460 174
        if (null === $value) {
1461
            throw new \InvalidArgumentException('$value must be a non null number');
1462
        }
1463
1464 174
        if (null !== $scale && $scale < 0) {
1465 2
            throw new \InvalidArgumentException('$scale must be a positive integer');
1466
        }
1467 173
    }
1468
1469
    /**
1470
     * @param string $sign
1471
     *
1472
     * @return string
1473
     */
1474 131
    private static function normalizeSign(string $sign): string
1475
    {
1476 131
        return ('+' === $sign) ? '' : $sign;
1477
    }
1478
1479
    /**
1480
     * Counts the number of significant digits of $val.
1481
     * Assumes a consistent internal state (without zeros at the end or the start).
1482
     *
1483
     * @param Decimal $val
1484
     * @param Decimal $abs $val->abs()
1485
     *
1486
     * @return int
1487
     */
1488 22
    private static function countSignificativeDigits(Decimal $val, Decimal $abs): int
1489
    {
1490 22
        return \strlen($val->value)
1491 22
            - (($abs->comp(DecimalConstants::one()) === -1) ? 2 : \max($val->scale, 1))
1492 22
            - ($val->isNegative() ? 1 : 0);
1493
    }
1494
}
1495