Passed
Push — master ( c327c2...0adcf0 )
by Ondřej
02:45
created

Decimal   F

Complexity

Total Complexity 130

Size/Duplication

Total Lines 667
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 667
rs 1.3673
c 0
b 0
f 0
wmc 130

34 Methods

Rating   Name   Duplication   Size   Complexity  
A toGMP() 0 8 3
B floor() 0 18 5
A toString() 0 3 2
A subtract() 0 11 3
A zero() 0 7 2
A getScale() 0 3 1
A isPositive() 0 3 2
A isNaN() 0 3 1
A round() 0 3 1
B ceil() 0 18 5
C fromNumber() 0 55 17
A isZero() 0 3 1
A NaN() 0 7 2
A lessThan() 0 3 1
A __toString() 0 3 1
B pow() 0 20 5
A sqrt() 0 10 3
A multiply() 0 11 3
A max() 0 7 2
D __construct() 0 78 26
A compareTo() 0 13 4
A abs() 0 6 2
A add() 0 11 3
B mod() 0 21 5
A negate() 0 8 4
A equals() 0 6 2
B factorial() 0 22 6
A divide() 0 11 3
A toFloat() 0 3 2
A isNegative() 0 3 2
B isInteger() 0 19 6
A toInt() 0 3 2
A greaterThan() 0 3 1
A min() 0 7 2

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.

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
namespace Ivory\Value;
4
5
use Ivory\Exception\UndefinedOperationException;
6
use Ivory\Value\Alg\IComparable;
7
use Ivory\Utils\System;
8
9
/**
10
 * A decimal number with fixed precision.
11
 *
12
 * The objects are immutable, i.e., operations always produce a new object.
13
 *
14
 * The representation and operations resemble the specification for numeric types in PostgreSQL, especially regarding
15
 * the precision of operation results. Generally, the scale of the result is computed such that the result is exactly
16
 * precise. However, operations which are inherently inexact have special treatment by PostgreSQL, which this class
17
 * tries to resemble: the result is of scale no less than any of the operands, and at least of a constant chosen such
18
 * that the result is at least as precise as the float8 operation would. This class uses similar result scale
19
 * computation and it also rounds the result at the computed scale, like PostgreSQL does (that is, the result is not
20
 * just truncated).
21
 *
22
 * The special static instance retrieved by {@link self::NaN()} represents the not-a-number value. In conformance with
23
 * PostgreSQL, a not-a-number only equals to a not-a-number, it is greater than any numeric value, and any operation
24
 * results in not-a-number if any of the operands is not-a-number.
25
 *
26
 * Regarding the implementation, using the GMP extension might be applicable for many operations, even for exact
27
 * operations on fractional numbers. The GMP extension is not built in PHP by default, however, so this is left for an
28
 * extension. Currently, only few integer operations use GMP if it is available. It would be better if an alternative
29
 * class was used if the presence of the GMP extension was discovered at runtime.
30
 *
31
 * @see http://www.postgresql.org/docs/current/static/datatype-numeric.html PostgreSQL Numeric Types
32
 * @see http://www.postgresql.org/docs/current/static/functions-math.html PostgreSQL Mathematical Functions and Operators
33
 */
34
class Decimal implements IComparable
35
{
36
    /** Maximal number of decimal digits considered by PostgreSQL. */
37
    const SCALE_LIMIT = 16383;
38
    /**
39
     * Minimal number of significant digits for inexact calculations, like division or square root. It reflects the
40
     * behaviour of PostgreSQL.
41
     */
42
    const MIN_SIG_DIGITS = 16;
43
44
    /** @var string|null string of decimal digits, including the sign and decimal point; null for the not-a-number */
45
    private $val;
46
    /** @var int number of decimal digits in the fractional part, to the right of the decimal point (if any) */
47
    private $scale;
48
49
    /**
50
     * @param string|int|float|Decimal|object $decimalNumber the value of the decimal number;
51
     *                                                       leading zeros are ignored (even after the minus sign), as
52
     *                                                         well as leading and trailing whitespace;
53
     *                                                       if an object is passed, it gets cast to string (which works
54
     *                                                         for {@link \GMP} objects, besides others)
55
     * @param int|null $scale the requested scale (i.e., the number of decimal digits in the fractional part) of the
56
     *                          number;
57
     *                        must be non-negative, or null;
58
     *                        if smaller than the scale of the given number, the number gets mathematically rounded to
59
     *                          the requested scale;
60
     *                        if greater than the scale of the given number, the number gets padded to have such many
61
     *                          decimal places;
62
     *                        if not given, it is computed from <tt>$decimalNumber</tt> automatically
63
     * @return Decimal
64
     */
65
    public static function fromNumber($decimalNumber, ?int $scale = null): Decimal
66
    {
67
        if ($decimalNumber === null) {
68
            throw new \InvalidArgumentException('decimalNumber');
69
        }
70
        if ($scale < 0) {
71
            throw new \InvalidArgumentException('scale');
72
        }
73
        if ($decimalNumber instanceof Decimal) {
74
            if ($scale === null || $scale == $decimalNumber->scale) {
75
                return $decimalNumber;
76
            } else {
77
                return $decimalNumber->round($scale);
78
            }
79
        }
80
81
        if (is_int($decimalNumber)) {
82
            $val = (string)$decimalNumber;
83
        } elseif (is_float($decimalNumber)) {
84
            $val = (string)$decimalNumber;
85
            $ePos = stripos($val, 'e');
86
            if ($ePos > 0) {
87
                $exp = (int)substr($val, $ePos + 1);
88
                $decs = substr($val, 0, $ePos);
89
                list($wp, $dp) = explode('.', $decs, 2);
90
91
                if ($exp >= 0) {
92
                    $dpLen = strlen($dp);
93
                    if ($exp >= $dpLen) {
94
                        $val = $wp . $dp . str_repeat('0', $exp - $dpLen);
95
                    } else {
96
                        $val = $wp . substr($dp, 0, $exp) . '.' . substr($dp, $exp);
97
                    }
98
                } else {
99
                    $mn = ($wp[0] == '-' ? 1 : 0);
100
                    $ord = strlen($wp) - $mn;
101
                    if (-$exp >= $ord) {
102
                        $prefix = ($mn ? '-' : '') . '0.' . str_repeat('0', -$exp - $ord);
103
                        if (($mn ? ($wp != '-0') : ($wp != '0'))) {
104
                            $prefix .= substr($wp, $mn);
105
                        }
106
                        $val = $prefix . $dp;
107
                    } else {
108
                        $val = substr($wp, 0, $mn + $ord + $exp) . '.' . substr($wp, $mn + $ord + $exp) . $dp;
109
                    }
110
                }
111
            }
112
        } else {
113
            if (!preg_match('~^\s*(-?)(?|(\.[0-9]+)|0*([0-9]+(?:\.[0-9]*)?))\s*$~', (string)$decimalNumber, $m)) {
114
                throw new \InvalidArgumentException('decimalNumber');
115
            }
116
            $val = $m[1] . $m[2]; // cuts off unnecessary leading zeros
117
        }
118
119
        return new Decimal($val, $scale);
120
    }
121
122
    /**
123
     * Gets the not-a-number special value. Returns the same object every time.
124
     *
125
     * It only equals to the not-a-number, and is greater than any number value. The result of any operation is, again,
126
     * the not-a-number.
127
     *
128
     * @return Decimal the not-a-number value
129
     */
130
    public static function NaN(): Decimal
0 ignored issues
show
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
131
    {
132
        static $dec = null;
133
        if ($dec === null) {
134
            $dec = new Decimal(null);
135
        }
136
        return $dec;
137
    }
138
139
    /**
140
     * Gets the decimal object representing the number zero. Returns the same object every time.
141
     *
142
     * @return Decimal
143
     */
144
    public static function zero(): Decimal
145
    {
146
        static $dec = null;
147
        if ($dec === null) {
148
            $dec = new Decimal('0');
149
        }
150
        return $dec;
151
    }
152
153
    /**
154
     * NOTE: The constructor is kept private so that extra sanity checks are performed on user input, while trusting the
155
     *       results of the operations without these extra checks.
156
     *
157
     * @param string|null $val
158
     * @param int|null $scale
159
     */
160
    private function __construct(?string $val, ?int $scale = null)
161
    {
162
        if ($val === null) {
163
            $this->val = null;
164
            $this->scale = null;
165
            return;
166
        }
167
168
        $this->val = $val;
169
170
        $decPoint = strpos($this->val, '.');
171
        if ($scale < 0) {
172
            $this->scale = 0;
173
            $minus = ($this->val[0] == '-' ? 1 : 0);
174
            if ($decPoint !== false) {
0 ignored issues
show
introduced by
The condition $decPoint !== false can never be false.
Loading history...
175
                $digits = substr($this->val, $minus, $decPoint - $minus);
176
            } else {
177
                $digits = ($minus ? substr($this->val, $minus) : $this->val);
178
            }
179
            $len = strlen($digits);
180
            $sigPos = $len + $scale;
181
            if ($sigPos < 0) {
182
                $this->val = '0';
183
                return;
184
            }
185
            $inc = ($digits[$sigPos] >= 5);
186
            if (!$inc && $sigPos == 0) {
187
                $this->val = '0';
188
                return;
189
            }
190
            for ($i = $sigPos; $i < $len; $i++) {
191
                $digits[$i] = '0';
192
            }
193
            if ($inc) {
194
                $augend = '1' . str_repeat('0', -$scale);
195
                $digits = bcadd($digits, $augend, 0);
196
            }
197
            $this->val = ($minus ? '-' : '') . $digits;
198
        } elseif ($decPoint === false) {
0 ignored issues
show
introduced by
The condition $decPoint === false can never be true.
Loading history...
199
            if ($scale > 0) {
200
                $this->val .= '.' . str_repeat('0', $scale);
201
                $this->scale = $scale;
202
            } else {
203
                $this->scale = 0;
204
            }
205
        } else {
206
            $len = strlen($this->val);
207
            $this->scale = $len - $decPoint - 1;
208
            $scale = min(self::SCALE_LIMIT, ($scale === null ? $this->scale : max(0, $scale)));
209
            if ($this->scale < $scale) {
210
                $this->val .= str_repeat('0', $scale - $this->scale);
211
                $this->scale = $scale;
212
            } elseif ($this->scale > $scale) {
213
                $newLen = $len - ($this->scale - $scale);
214
                $inc = ($this->val[$newLen] >= 5);
215
                if ($scale == 0) {
216
                    $newLen--;
217
                }
218
                $this->val = substr($this->val, 0, $newLen);
219
                if ($inc) {
220
                    $mn = ($this->val[0] == '-' ? '-' : '');
221
                    $augend = $mn . ($scale == 0 ? '1' : '.' . str_repeat('0', $scale - 1) . '1');
222
                    $this->val = bcadd($this->val, $augend, $scale);
223
                }
224
                $this->scale = $scale;
225
            }
226
227
            if ($this->val[0] == '.') {
228
                $this->val = '0' . $this->val;
229
            } elseif ($this->val[0] == '-' && $this->val[1] == '.') {
230
                $this->val[0] = '0';
231
                $this->val = '-' . $this->val;
232
            }
233
        }
234
235
        // eliminate negative zero - PostgreSQL does not differentiate between zeros
236
        if ($this->val[0] == '-' && strspn($this->val, '-0.') == strlen($this->val)) {
237
            $this->val = substr($this->val, 1);
238
        }
239
    }
240
241
242
    /**
243
     * @return int|null number of decimal digits in the fractional part, to the right of the decimal point (if any), or
244
     *                  <tt>null</tt> for the not-a-number
245
     */
246
    public function getScale(): ?int
247
    {
248
        return $this->scale;
249
    }
250
251
    /**
252
     * Compare this number numerically with another number.
253
     *
254
     * Note that using the `==` operator checks that the two {@link Decimal} objects are of the same value and scale,
255
     * which might or might not be desired. Such a difference only arises in the trailing fractional zero digits,
256
     * though.
257
     *
258
     * @param string|int|float|Decimal|object $number number to compare this number with
259
     * @return bool whether this number numerically equals to <tt>$number</tt>
260
     */
261
    public function equals($number): bool
262
    {
263
        if ($number === null) {
264
            return false;
265
        }
266
        return ($this->compareTo($number) == 0);
267
    }
268
269
    /**
270
     * @param string|int|float|Decimal|object $number
271
     * @return bool <tt>true</tt> iff this number is numerically less than <tt>$number</tt>
272
     */
273
    public function lessThan($number): bool
274
    {
275
        return ($this->compareTo($number) < 0);
276
    }
277
278
    /**
279
     * @param string|int|float|Decimal|object $number
280
     * @return bool <tt>true</tt> iff this number is numerically greater than <tt>$number</tt>
281
     */
282
    public function greaterThan($number): bool
283
    {
284
        return ($this->compareTo($number) > 0);
285
    }
286
287
    /**
288
     * @param string|int|float|Decimal|object $number number to compare this number with
289
     * @return int -1, 0, or 1 if this number is numerically less than, equal to, or greater than <tt>$number</tt>
290
     */
291
    public function compareTo($number): int
292
    {
293
        $arg = self::fromNumber($number);
294
        if ($this->isNaN()) {
295
            if ($arg->isNaN()) {
296
                return 0;
297
            } else {
298
                return 1;
299
            }
300
        } elseif ($arg->isNaN()) {
301
            return -1;
302
        } else {
303
            return bccomp($this->val, $arg->val, max($this->scale, $arg->scale));
304
        }
305
    }
306
307
    /**
308
     * @return bool whether this is a not-a-number
309
     */
310
    public function isNaN(): bool
311
    {
312
        return ($this->val === null);
313
    }
314
315
    /**
316
     * @return bool whether this is zero
317
     */
318
    public function isZero(): bool
319
    {
320
        return $this->equals(self::zero());
321
    }
322
323
    /**
324
     * @return bool whether this is a positive number
325
     */
326
    public function isPositive(): bool
327
    {
328
        return (!$this->isNaN() && $this->compareTo(self::zero()) > 0);
329
    }
330
331
    /**
332
     * @return bool whether this is a negative number
333
     */
334
    public function isNegative(): bool
335
    {
336
        return (!$this->isNaN() && $this->val[0] == '-');
337
    }
338
339
    /**
340
     * @return bool whether this is an integer, i.e., a number without decimal part (or the decimal part equal to zero)
341
     */
342
    public function isInteger(): bool
343
    {
344
        if ($this->isNaN()) {
345
            return false;
346
        }
347
        if (strpos($this->val, '.') === false) {
348
            return true;
349
        }
350
        for ($i = strlen($this->val) - 1; ; $i--) {
351
            switch ($this->val[$i]) {
352
                case '.':
353
                    return true;
354
                case '0':
355
                    break;
356
                default:
357
                    return false;
358
            }
359
        }
360
        return true;
361
    }
362
363
    /**
364
     * @param string|int|float|Decimal|object $augend
365
     * @return Decimal a new decimal number, representing the result of sum of this and the given number
366
     */
367
    public function add($augend): Decimal
368
    {
369
        if ($this->isNaN()) {
370
            return $this;
371
        }
372
        $arg = self::fromNumber($augend);
373
        if ($arg->isNaN()) {
374
            return $arg;
375
        }
376
        $scale = max($this->scale, $arg->scale);
377
        return new Decimal(bcadd($this->val, $arg->val, $scale + 1), $scale);
378
    }
379
380
    /**
381
     * @param string|int|float|Decimal|object $subtrahend
382
     * @return Decimal a new decimal number, representing the result of subtraction of this and the given number
383
     */
384
    public function subtract($subtrahend): Decimal
385
    {
386
        if ($this->isNaN()) {
387
            return $this;
388
        }
389
        $arg = self::fromNumber($subtrahend);
390
        if ($arg->isNaN()) {
391
            return $arg;
392
        }
393
        $scale = max($this->scale, $arg->scale);
394
        return new Decimal(bcsub($this->val, $arg->val, $scale + 1), $scale);
395
    }
396
397
    /**
398
     * @param string|int|float|Decimal|object $multiplicand
399
     * @return Decimal a new decimal number, representing the result of multiplication of this and the given number
400
     */
401
    public function multiply($multiplicand): Decimal
402
    {
403
        if ($this->isNaN()) {
404
            return $this;
405
        }
406
        $arg = self::fromNumber($multiplicand);
407
        if ($arg->isNaN()) {
408
            return $arg;
409
        }
410
        $scale = min($this->scale + $arg->scale, self::SCALE_LIMIT);
411
        return new Decimal(bcmul($this->val, $arg->val, $scale + 1), $scale);
412
    }
413
414
    /**
415
     * @param string|int|float|Decimal|object $divisor
416
     * @return Decimal a new decimal number, representing the result of division of this number with the given number
417
     * @throws \RuntimeException if <tt>$divisor</tt> is zero
418
     */
419
    public function divide($divisor): Decimal
420
    {
421
        if ($this->isNaN()) {
422
            return $this;
423
        }
424
        $arg = self::fromNumber($divisor);
425
        if ($arg->isNaN()) {
426
            return $arg;
427
        }
428
        $scale = max($this->scale, $arg->scale, self::MIN_SIG_DIGITS);
429
        return new Decimal(bcdiv($this->val, $arg->val, $scale + 1), $scale);
430
    }
431
432
    /**
433
     * @param string|int|float|Decimal|object $modulus
434
     * @return Decimal remainder of this number divided by <tt>$modulus</tt>
435
     * @throws \RuntimeException if <tt>$modulus</tt> is zero
436
     */
437
    public function mod($modulus): Decimal
438
    {
439
        if ($this->isNaN()) {
440
            return $this;
441
        }
442
        $arg = self::fromNumber($modulus);
443
        if ($arg->isNaN()) {
444
            return $arg;
445
        }
446
        if ($arg->isZero()) {
447
            throw new \RuntimeException('Division by zero');
448
        }
449
450
        // NOTE: bcmod() only calculates integer modulus
451
        $a = $this->abs();
452
        $b = $arg->abs();
453
        $m = $a->subtract($b->multiply($a->divide($b)->floor()));
454
        if ($this->isNegative()) {
455
            return $m->negate();
456
        } else {
457
            return $m;
458
        }
459
    }
460
461
    /**
462
     * @todo more precise calculation of fractional powers - now only double precision is used
463
     * @param string|int|float|Decimal|object $power
464
     * @return Decimal this number powered to <tt>$number</tt>
465
     * @throws \RuntimeException if this number is negative while <tt>$power</tt> is non-integer (that would lead to a
466
     *                             complex result)
467
     */
468
    public function pow($power): Decimal
469
    {
470
        if ($this->isNaN()) {
471
            return $this;
472
        }
473
        $arg = self::fromNumber($power);
474
        if ($arg->isNaN()) {
475
            return $arg;
476
        }
477
478
        $scale = max($this->scale, $arg->scale, self::MIN_SIG_DIGITS);
479
480
        // NOTE: bcpow() only takes integer powers
481
        if ($arg->isInteger()) {
482
            return new Decimal(bcpow($this->val, $arg->val, $scale + 1), $scale);
483
        } else {
484
            if ($this->isNegative()) {
485
                throw new \RuntimeException('Negative number raised to a non-integer power yields a complex result');
486
            }
487
            return self::fromNumber(pow($this->toFloat(), $arg->toFloat()));
488
        }
489
    }
490
491
    /**
492
     * @return Decimal the absolute value of this number
493
     */
494
    public function abs(): Decimal
495
    {
496
        if ($this->isNegative()) {
497
            return $this->negate();
498
        } else {
499
            return $this;
500
        }
501
    }
502
503
    /**
504
     * @return Decimal a new decimal number, representing the negative value of this number
505
     */
506
    public function negate(): Decimal
507
    {
508
        if ($this->isNaN() || $this->isZero()) {
509
            return $this;
510
        } elseif ($this->val[0] == '-') {
511
            return new Decimal(substr($this->val, 1));
512
        } else {
513
            return new Decimal('-' . $this->val);
514
        }
515
    }
516
517
    /**
518
     * Computes the exact factorial of this number.
519
     * Only defined on numbers of scale zero, i.e., those without decimal part, and on the not-a-number.
520
     *
521
     * In conformance with PostgreSQL, the factorial of non-positive numbers is defined to be 1.
522
     *
523
     * @return Decimal the factorial of this number
524
     * @throws UndefinedOperationException if this number is neither a zero-scale number nor the not-a-number
525
     */
526
    public function factorial(): Decimal
527
    {
528
        if ($this->isNaN()) {
529
            return $this;
530
        }
531
        if ($this->scale > 0) {
532
            throw new UndefinedOperationException('Number with a decimal part');
533
        }
534
        if ($this->lessThan(2)) {
535
            return new Decimal('1');
536
        }
537
538
        if (System::hasGMP()) {
539
            return new Decimal(gmp_strval(gmp_fact($this->toGMP())));
0 ignored issues
show
Bug introduced by
$this->toGMP() of type GMP is incompatible with the type resource|string expected by parameter $a of gmp_fact(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

539
            return new Decimal(gmp_strval(gmp_fact(/** @scrutinizer ignore-type */ $this->toGMP())));
Loading history...
540
        }
541
542
        // OPT: there are more efficient algorithms calculating the factorial; see, e.g., http://www.luschny.de/math/factorial/index.html
543
        $result = new Decimal('2');
544
        for ($i = new Decimal('3'); $i->compareTo($this) <= 0; $i = $i->add(1)) {
545
            $result = $result->multiply($i);
546
        }
547
        return $result;
548
    }
549
550
    /**
551
     * @param int $scale number of decimal places to round this number to; may be negative to round to higher orders
552
     * @return Decimal a new decimal number, representing this number rounded to <tt>$scale</tt> decimal places
553
     */
554
    public function round(int $scale = 0): Decimal
555
    {
556
        return new Decimal($this->val, $scale);
557
    }
558
559
    /**
560
     * @return Decimal largest integer not greater than this number
561
     */
562
    public function floor(): Decimal
563
    {
564
        if ($this->isNaN()) {
565
            return $this;
566
        }
567
568
        $decPoint = strpos($this->val, '.');
569
        if ($decPoint === false) {
0 ignored issues
show
introduced by
The condition $decPoint === false can never be true.
Loading history...
570
            return $this;
571
        }
572
573
        if ($this->val[0] != '-') {
574
            return new Decimal(substr($this->val, 0, $decPoint));
575
        } elseif ($this->isInteger()) {
576
            return new Decimal($this->val, 0);
577
        } else {
578
            // negative number with non-zero fractional part
579
            return new Decimal(bcsub(substr($this->val, 0, $decPoint), '1', 0));
580
        }
581
    }
582
583
    /**
584
     * @return Decimal smallest integer not less than this number
585
     */
586
    public function ceil(): Decimal
587
    {
588
        if ($this->isNaN()) {
589
            return $this;
590
        }
591
592
        $decPoint = strpos($this->val, '.');
593
        if ($decPoint === false) {
0 ignored issues
show
introduced by
The condition $decPoint === false can never be true.
Loading history...
594
            return $this;
595
        }
596
597
        if ($this->val[0] == '-') {
598
            return new Decimal(substr($this->val, 0, $decPoint));
599
        } elseif ($this->isInteger()) {
600
            return new Decimal($this->val, 0);
601
        } else {
602
            // non-negative number with non-zero fractional part
603
            return new Decimal(bcadd(substr($this->val, 0, $decPoint), '1', 0));
604
        }
605
    }
606
607
    /**
608
     * @param string|int|float|Decimal|object $number
609
     * @return Decimal the greater of this number and <tt>$number</tt>, preferably this number if numerically equal
610
     */
611
    public function max($number): Decimal
612
    {
613
        $arg = self::fromNumber($number);
614
        if ($this->lessThan($arg)) {
615
            return $arg;
616
        } else {
617
            return $this;
618
        }
619
    }
620
621
    /**
622
     * @param string|int|float|Decimal|object $number
623
     * @return Decimal the smaller of this number and <tt>$number</tt>, preferably this number if numerically equal
624
     */
625
    public function min($number): Decimal
626
    {
627
        $arg = self::fromNumber($number);
628
        if ($this->greaterThan($arg)) {
629
            return $arg;
630
        } else {
631
            return $this;
632
        }
633
    }
634
635
    /**
636
     * @return Decimal a new decimal number, representing the square root of this number
637
     * @throws UndefinedOperationException if this is a negative number
638
     */
639
    public function sqrt(): Decimal
640
    {
641
        if ($this->isNaN()) {
642
            return $this;
643
        }
644
        if ($this->isNegative()) {
645
            throw new UndefinedOperationException('square root of negative number');
646
        }
647
        $scale = max($this->scale, self::MIN_SIG_DIGITS);
648
        return new Decimal(bcsqrt($this->val, $scale + 1));
649
    }
650
651
652
    /**
653
     * @return int|null the value of this number cast explicitly to <tt>int</tt>;
654
     *                  for big numbers, this yields the maximal available integer value (i.e., <tt>PHP_INT_MAX</tt> or
655
     *                    <tt>PHP_INT_MIN</tt>);
656
     *                  <tt>null</tt> is returned if this is the not-a-number
657
     */
658
    public function toInt(): ?int
659
    {
660
        return ($this->val === null ? null : (int)$this->val);
661
    }
662
663
    /**
664
     * @return float the value of this number cast explicitly to <tt>float</tt>, or the float <tt>NAN</tt> value if this
665
     *                 is the not-a-number
666
     */
667
    public function toFloat(): float
668
    {
669
        return ($this->val === null ? NAN : (float)$this->val);
670
    }
671
672
    /**
673
     * @return string the value of this number, or the special string <tt>'NaN'</tt> for the not-a-number value
674
     */
675
    public function toString(): string
676
    {
677
        return ($this->val === null ? 'NaN' : $this->val);
678
    }
679
680
    /**
681
     * Converts the value to a {@link \GMP} object.
682
     *
683
     * Requires the `gmp` PHP extension.
684
     *
685
     * @return \GMP a {@link \GMP} object representing the same integer as this number
686
     * @throws UndefinedOperationException if this number is not an integer, i.e., has some non-zero fractional digits
687
     */
688
    public function toGMP()
689
    {
690
        if (!$this->isInteger()) {
691
            throw new UndefinedOperationException('toGMP() is only defined for integers');
692
        }
693
        $decPoint = strpos($this->val, '.');
694
        $str = ($decPoint === false ? $this->val : substr($this->val, 0, $decPoint - 1));
0 ignored issues
show
introduced by
The condition $decPoint === false can never be true.
Loading history...
695
        return gmp_init($str);
0 ignored issues
show
Bug Best Practice introduced by
The expression return gmp_init($str) returns the type resource which is incompatible with the documented return type GMP.
Loading history...
696
    }
697
698
    public function __toString()
699
    {
700
        return $this->toString();
701
    }
702
}
703