Decimal   D
last analyzed

Complexity

Total Complexity 194

Size/Duplication

Total Lines 1346
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 96.68%

Importance

Changes 15
Bugs 1 Features 0
Metric Value
wmc 194
c 15
b 1
f 0
lcom 1
cbo 4
dl 0
loc 1346
ccs 524
cts 542
cp 0.9668
rs 4.4102

61 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A __clone() 0 3 1
A fromInteger() 0 6 1
A fromDecimal() 0 14 3
A sub() 0 9 1
B div() 0 35 4
A sqrt() 0 17 4
A log10() 0 17 4
A isZero() 0 6 2
A isPositive() 0 4 2
A isNegative() 0 4 1
A isInteger() 0 4 1
A equals() 0 18 3
A comp() 0 16 3
B create() 0 17 6
D fromFloat() 0 34 10
C fromString() 0 33 7
B pow() 0 55 9
A isGreaterThan() 0 4 1
A isGreaterOrEqualTo() 0 6 2
A isLessThan() 0 4 1
A isLessOrEqualTo() 0 6 2
A additiveInverse() 0 12 3
A round() 0 8 2
A ceil() 0 12 3
B innerTruncate() 0 23 5
A floor() 0 12 3
A abs() 0 6 3
A mod() 0 5 1
A cosec() 0 11 2
A sec() 0 11 2
C arcsin() 0 26 7
C arccos() 0 30 7
B arctan() 0 22 5
B arccot() 0 25 5
B arcsec() 0 27 6
B arccsc() 0 23 6
A exp() 0 15 3
B factorialSerie() 0 24 3
B powerSerie() 0 40 5
B simplePowerSerie() 0 29 5
A tan() 0 11 2
A cotan() 0 11 2
A hasSameSign() 0 4 4
A asFloat() 0 4 1
A asInteger() 0 4 1
A innerValue() 0 4 1
A __toString() 0 4 1
B fromExpNotationString() 0 30 2
A innerRound() 0 15 4
A sin() 0 19 4
A cos() 0 19 4
B innerLog10() 0 37 4
B innerPowWithLittleExponent() 0 28 3
A computeSquareIndex() 0 18 2
A compute2NRoot() 0 10 2
A paramsValidation() 0 10 4
A normalizeSign() 0 8 2
A countSignificativeDigits() 0 6 3
A mul() 0 13 2
A add() 0 9 1

How to fix   Complexity   

Complex Class

Complex classes like Decimal often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Decimal, and based on these observations, apply Extract Interface, too.

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