Completed
Push — master ( 8a9a36...5d30b0 )
by Ondřej
03:00
created

Decimal::ceil()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 12
nc 5
nop 0
1
<?php
2
declare(strict_types=1);
3
4
namespace Ivory\Value;
5
6
use Ivory\Exception\UndefinedOperationException;
7
use Ivory\Utils\IEqualable;
8
use Ivory\Utils\System;
9
10
/**
11
 * A decimal number with fixed precision.
12
 *
13
 * The objects are immutable, i.e., operations always produce a new object.
14
 *
15
 * The representation and operations resemble the specification for numeric types in PostgreSQL, especially regarding
16
 * the precision of operation results. Generally, the scale of the result is computed such that the result is exactly
17
 * precise. However, operations which are inherently inexact have special treatment by PostgreSQL, which this class
18
 * tries to resemble: the result is of scale no less than any of the operands, and at least of a constant chosen such
19
 * that the result is at least as precise as the float8 operation would. This class uses similar result scale
20
 * computation and it also rounds the result at the computed scale, like PostgreSQL does (that is, the result is not
21
 * just truncated).
22
 *
23
 * The special static instance retrieved by {@link self::NaN()} represents the not-a-number value. In conformance with
24
 * PostgreSQL, a not-a-number only equals to a not-a-number, it is greater than any numeric value, and any operation
25
 * results in not-a-number if any of the operands is not-a-number.
26
 *
27
 * Regarding the implementation, using the GMP extension might be applicable for many operations, even for exact
28
 * operations on fractional numbers. The GMP extension is not built in PHP by default, however, so this is left for an
29
 * extension. Currently, only few integer operations use GMP if it is available. It would be better if an alternative
30
 * class was used if the presence of the GMP extension was discovered at runtime.
31
 *
32
 * @see http://www.postgresql.org/docs/current/static/datatype-numeric.html PostgreSQL Numeric Types
33
 * @see http://www.postgresql.org/docs/current/static/functions-math.html PostgreSQL Mathematical Functions and Operators
34
 */
35
class Decimal implements IEqualable
0 ignored issues
show
Coding Style introduced by
Since you have declared the constructor as private, maybe you should also declare the class as final.
Loading history...
36
{
37
    /** Maximal number of decimal digits considered by PostgreSQL. */
38
    const SCALE_LIMIT = 16383;
39
    /**
40
     * Minimal number of significant digits for inexact calculations, like division or square root. It reflects the
41
     * behaviour of PostgreSQL.
42
     */
43
    const MIN_SIG_DIGITS = 16;
44
45
    /** @var string|null string of decimal digits, including the sign and decimal point; null for the not-a-number */
46
    private $val;
47
    /** @var int number of decimal digits in the fractional part, to the right of the decimal point (if any) */
48
    private $scale;
49
50
    /**
51
     * @param string|int|float|Decimal|object $decimalNumber the value of the decimal number;
52
     *                                                       leading zeros are ignored (even after the minus sign), as
53
     *                                                         well as leading and trailing whitespace;
54
     *                                                       if an object is passed, it gets cast to string (which works
55
     *                                                         for {@link \GMP} objects, besides others)
56
     * @param int|null $scale the requested scale (i.e., the number of decimal digits in the fractional part) of the
57
     *                          number;
58
     *                        must be non-negative, or null;
59
     *                        if smaller than the scale of the given number, the number gets mathematically rounded to
60
     *                          the requested scale;
61
     *                        if greater than the scale of the given number, the number gets padded to have such many
62
     *                          decimal places;
63
     *                        if not given, it is computed from <tt>$decimalNumber</tt> automatically
64
     * @return Decimal
65
     */
66
    public static function fromNumber($decimalNumber, ?int $scale = null): Decimal
67
    {
68
        if ($decimalNumber === null) {
69
            throw new \InvalidArgumentException('decimalNumber');
70
        }
71
        if ($scale < 0) {
72
            throw new \InvalidArgumentException('scale');
73
        }
74
        if ($decimalNumber instanceof Decimal) {
75
            if ($scale === null || $scale == $decimalNumber->scale) {
76
                return $decimalNumber;
77
            } else {
78
                return $decimalNumber->round($scale);
79
            }
80
        }
81
82
        if (is_int($decimalNumber)) {
83
            $val = (string)$decimalNumber;
84
        } elseif (is_float($decimalNumber)) {
85
            $val = (string)$decimalNumber;
86
            $ePos = stripos($val, 'e');
87
            if ($ePos > 0) {
88
                $exp = (int)substr($val, $ePos + 1);
89
                $decs = substr($val, 0, $ePos);
90
                list($wp, $dp) = explode('.', $decs, 2);
91
92
                if ($exp >= 0) {
93
                    $dpLen = strlen($dp);
94
                    if ($exp >= $dpLen) {
95
                        $val = $wp . $dp . str_repeat('0', $exp - $dpLen);
96
                    } else {
97
                        $val = $wp . substr($dp, 0, $exp) . '.' . substr($dp, $exp);
98
                    }
99
                } else {
100
                    $mn = ($wp[0] == '-' ? 1 : 0);
101
                    $ord = strlen($wp) - $mn;
102
                    if (-$exp >= $ord) {
103
                        $prefix = ($mn ? '-' : '') . '0.' . str_repeat('0', -$exp - $ord);
104
                        if (($mn ? ($wp != '-0') : ($wp != '0'))) {
105
                            $prefix .= substr($wp, $mn);
106
                        }
107
                        $val = $prefix . $dp;
108
                    } else {
109
                        $val = substr($wp, 0, $mn + $ord + $exp) . '.' . substr($wp, $mn + $ord + $exp) . $dp;
110
                    }
111
                }
112
            }
113
        } else {
114
            if (!preg_match('~^\s*(-?)(?|(\.[0-9]+)|0*([0-9]+(?:\.[0-9]*)?))\s*$~', (string)$decimalNumber, $m)) {
115
                throw new \InvalidArgumentException('decimalNumber');
116
            }
117
            $val = $m[1] . $m[2]; // cuts off unnecessary leading zeros
118
        }
119
120
        return new Decimal($val, $scale);
121
    }
122
123
    /**
124
     * Gets the not-a-number special value. Returns the same object every time.
125
     *
126
     * It only equals to the not-a-number, and is greater than any number value. The result of any operation is, again,
127
     * the not-a-number.
128
     *
129
     * @return Decimal the not-a-number value
130
     */
131
    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...
132
    {
133
        static $dec = null;
134
        if ($dec === null) {
135
            $dec = new Decimal(null);
136
        }
137
        return $dec;
138
    }
139
140
    /**
141
     * Gets the decimal object representing the number zero. Returns the same object every time.
142
     *
143
     * @return Decimal
144
     */
145
    public static function zero(): Decimal
146
    {
147
        static $dec = null;
148
        if ($dec === null) {
149
            $dec = new Decimal('0');
150
        }
151
        return $dec;
152
    }
153
154
    /**
155
     * NOTE: The constructor is kept private so that extra sanity checks are performed on user input, while trusting the
156
     *       results of the operations without these extra checks.
157
     *
158
     * @param string|null $val
159
     * @param int|null $scale
160
     */
161
    private function __construct(?string $val, ?int $scale = null)
162
    {
163
        if ($val === null) {
164
            $this->val = null;
165
            $this->scale = null;
166
            return;
167
        }
168
169
        $this->val = $val;
170
171
        $decPoint = strpos($this->val, '.');
172
        if ($scale < 0) {
173
            $this->scale = 0;
174
            $minus = ($this->val[0] == '-' ? 1 : 0);
175
            if ($decPoint !== false) {
176
                $digits = substr($this->val, $minus, $decPoint - $minus);
177
            } else {
178
                $digits = ($minus ? substr($this->val, $minus) : $this->val);
179
            }
180
            $len = strlen($digits);
181
            $sigPos = $len + $scale;
182
            if ($sigPos < 0) {
183
                $this->val = '0';
184
                return;
185
            }
186
            $inc = ($digits[$sigPos] >= 5);
187
            if (!$inc && $sigPos == 0) {
188
                $this->val = '0';
189
                return;
190
            }
191
            for ($i = $sigPos; $i < $len; $i++) {
192
                $digits[$i] = '0';
193
            }
194
            if ($inc) {
195
                $augend = '1' . str_repeat('0', -$scale);
196
                $digits = bcadd($digits, $augend, 0);
197
            }
198
            $this->val = ($minus ? '-' : '') . $digits;
199
        } elseif ($decPoint === false) {
200
            if ($scale > 0) {
201
                $this->val .= '.' . str_repeat('0', $scale);
202
                $this->scale = $scale;
203
            } else {
204
                $this->scale = 0;
205
            }
206
        } else {
207
            $len = strlen($this->val);
208
            $this->scale = $len - $decPoint - 1;
209
            $scale = min(self::SCALE_LIMIT, ($scale === null ? $this->scale : max(0, $scale)));
210
            if ($this->scale < $scale) {
211
                $this->val .= str_repeat('0', $scale - $this->scale);
212
                $this->scale = $scale;
213
            } elseif ($this->scale > $scale) {
214
                $newLen = $len - ($this->scale - $scale);
215
                $inc = ($this->val[$newLen] >= 5);
216
                if ($scale == 0) {
217
                    $newLen--;
218
                }
219
                $this->val = substr($this->val, 0, $newLen);
220
                if ($inc) {
221
                    $mn = ($this->val[0] == '-' ? '-' : '');
222
                    $augend = $mn . ($scale == 0 ? '1' : '.' . str_repeat('0', $scale - 1) . '1');
223
                    $this->val = bcadd($this->val, $augend, $scale);
224
                }
225
                $this->scale = $scale;
226
            }
227
228
            if ($this->val[0] == '.') {
229
                $this->val = '0' . $this->val;
230
            } elseif ($this->val[0] == '-' && $this->val[1] == '.') {
231
                $this->val[0] = '0';
232
                $this->val = '-' . $this->val;
233
            }
234
        }
235
236
        // eliminate negative zero - PostgreSQL does not differentiate between zeros
237
        if ($this->val[0] == '-' && strspn($this->val, '-0.') == strlen($this->val)) {
238
            $this->val = substr($this->val, 1);
239
        }
240
    }
241
242
243
    /**
244
     * @return int|null number of decimal digits in the fractional part, to the right of the decimal point (if any), or
245
     *                  <tt>null</tt> for the not-a-number
246
     */
247
    public function getScale(): ?int
248
    {
249
        return $this->scale;
250
    }
251
252
    /**
253
     * Compare this number numerically with another number.
254
     *
255
     * Note that using the `==` operator checks that the two {@link Decimal} objects are of the same value and scale,
256
     * which might or might not be desired. Such a difference only arises in the trailing fractional zero digits,
257
     * though.
258
     *
259
     * @param string|int|float|Decimal|object $number number to compare this number with
260
     * @return bool|null whether this number numerically equals to <tt>$number</tt>;
261
     *                   <tt>null</tt> for <tt>null</tt> input
262
     */
263
    public function equals($number): ?bool
264
    {
265
        if ($number === null) {
266
            return null;
267
        }
268
        return ($this->compareTo($number) == 0);
269
    }
270
271
    /**
272
     * @param string|int|float|Decimal|object $number
273
     * @return bool <tt>true</tt> iff this number is numerically less than <tt>$number</tt>
274
     */
275
    public function lessThan($number): bool
276
    {
277
        return ($this->compareTo($number) < 0);
278
    }
279
280
    /**
281
     * @param string|int|float|Decimal|object $number
282
     * @return bool <tt>true</tt> iff this number is numerically greater than <tt>$number</tt>
283
     */
284
    public function greaterThan($number): bool
285
    {
286
        return ($this->compareTo($number) > 0);
287
    }
288
289
    /**
290
     * @param string|int|float|Decimal|object $number number to compare this number with
291
     * @return int -1, 0, or 1 if this number is numerically less than, equal to, or greater than <tt>$number</tt>
292
     */
293
    public function compareTo($number): int
294
    {
295
        $arg = self::fromNumber($number);
296
        if ($this->isNaN()) {
297
            if ($arg->isNaN()) {
298
                return 0;
299
            } else {
300
                return 1;
301
            }
302
        } elseif ($arg->isNaN()) {
303
            return -1;
304
        } else {
305
            return bccomp($this->val, $arg->val, max($this->scale, $arg->scale));
306
        }
307
    }
308
309
    /**
310
     * @return bool whether this is a not-a-number
311
     */
312
    public function isNaN(): bool
313
    {
314
        return ($this->val === null);
315
    }
316
317
    /**
318
     * @return bool whether this is zero
319
     */
320
    public function isZero(): bool
321
    {
322
        return ($this->equals(self::zero()));
323
    }
324
325
    /**
326
     * @return bool whether this is a positive number
327
     */
328
    public function isPositive(): bool
329
    {
330
        return (!$this->isNaN() && $this->compareTo(self::zero()) > 0);
331
    }
332
333
    /**
334
     * @return bool whether this is a negative number
335
     */
336
    public function isNegative(): bool
337
    {
338
        return (!$this->isNaN() && $this->val[0] == '-');
339
    }
340
341
    /**
342
     * @return bool whether this is an integer, i.e., a number without decimal part (or the decimal part equal to zero)
343
     */
344
    public function isInteger(): bool
345
    {
346
        if ($this->isNaN()) {
347
            return false;
348
        }
349
        if (strpos($this->val, '.') === false) {
350
            return true;
351
        }
352
        for ($i = strlen($this->val) - 1; ; $i--) {
353
            switch ($this->val[$i]) {
354
                case '.':
355
                    return true;
356
                case '0':
357
                    break;
358
                default:
359
                    return false;
360
            }
361
        }
362
        return true;
363
    }
364
365
    /**
366
     * @param string|int|float|Decimal|object $augend
367
     * @return Decimal a new decimal number, representing the result of sum of this and the given number
368
     */
369
    public function add($augend): Decimal
370
    {
371
        if ($this->isNaN()) {
372
            return $this;
373
        }
374
        $arg = self::fromNumber($augend);
375
        if ($arg->isNaN()) {
376
            return $arg;
377
        }
378
        $scale = max($this->scale, $arg->scale);
379
        return new Decimal(bcadd($this->val, $arg->val, $scale + 1), $scale);
380
    }
381
382
    /**
383
     * @param string|int|float|Decimal|object $subtrahend
384
     * @return Decimal a new decimal number, representing the result of subtraction of this and the given number
385
     */
386
    public function subtract($subtrahend): Decimal
387
    {
388
        if ($this->isNaN()) {
389
            return $this;
390
        }
391
        $arg = self::fromNumber($subtrahend);
392
        if ($arg->isNaN()) {
393
            return $arg;
394
        }
395
        $scale = max($this->scale, $arg->scale);
396
        return new Decimal(bcsub($this->val, $arg->val, $scale + 1), $scale);
397
    }
398
399
    /**
400
     * @param string|int|float|Decimal|object $multiplicand
401
     * @return Decimal a new decimal number, representing the result of multiplication of this and the given number
402
     */
403
    public function multiply($multiplicand): Decimal
404
    {
405
        if ($this->isNaN()) {
406
            return $this;
407
        }
408
        $arg = self::fromNumber($multiplicand);
409
        if ($arg->isNaN()) {
410
            return $arg;
411
        }
412
        $scale = min($this->scale + $arg->scale, self::SCALE_LIMIT);
413
        return new Decimal(bcmul($this->val, $arg->val, $scale + 1), $scale);
414
    }
415
416
    /**
417
     * @param string|int|float|Decimal|object $divisor
418
     * @return Decimal a new decimal number, representing the result of division of this number with the given number
419
     * @throws \RuntimeException if <tt>$divisor</tt> is zero
420
     */
421
    public function divide($divisor): Decimal
422
    {
423
        if ($this->isNaN()) {
424
            return $this;
425
        }
426
        $arg = self::fromNumber($divisor);
427
        if ($arg->isNaN()) {
428
            return $arg;
429
        }
430
        $scale = max($this->scale, $arg->scale, self::MIN_SIG_DIGITS);
431
        return new Decimal(bcdiv($this->val, $arg->val, $scale + 1), $scale);
432
    }
433
434
    /**
435
     * @param string|int|float|Decimal|object $modulus
436
     * @return Decimal remainder of this number divided by <tt>$modulus</tt>
437
     * @throws \RuntimeException if <tt>$modulus</tt> is zero
438
     */
439
    public function mod($modulus): Decimal
440
    {
441
        if ($this->isNaN()) {
442
            return $this;
443
        }
444
        $arg = self::fromNumber($modulus);
445
        if ($arg->isNaN()) {
446
            return $arg;
447
        }
448
        if ($arg->isZero()) {
449
            throw new \RuntimeException('Division by zero');
450
        }
451
452
        // NOTE: bcmod() only calculates integer modulus
453
        $a = $this->abs();
454
        $b = $arg->abs();
455
        $m = $a->subtract($b->multiply($a->divide($b)->floor()));
456
        if ($this->isNegative()) {
457
            return $m->negate();
458
        } else {
459
            return $m;
460
        }
461
    }
462
463
    /**
464
     * @todo more precise calculation of fractional powers - now only double precision is used
465
     * @param string|int|float|Decimal|object $power
466
     * @return Decimal this number powered to <tt>$number</tt>
467
     * @throws \RuntimeException if this number is negative while <tt>$power</tt> is non-integer (that would lead to a
468
     *                             complex result)
469
     */
470
    public function pow($power): Decimal
471
    {
472
        if ($this->isNaN()) {
473
            return $this;
474
        }
475
        $arg = self::fromNumber($power);
476
        if ($arg->isNaN()) {
477
            return $arg;
478
        }
479
480
        $scale = max($this->scale, $arg->scale, self::MIN_SIG_DIGITS);
481
482
        // NOTE: bcpow() only takes integer powers
483
        if ($arg->isInteger()) {
484
            return new Decimal(bcpow($this->val, $arg->val, $scale + 1), $scale);
485
        } else {
486
            if ($this->isNegative()) {
487
                throw new \RuntimeException('Negative number raised to a non-integer power yields a complex result');
488
            }
489
            return self::fromNumber(pow($this->toFloat(), $arg->toFloat()));
490
        }
491
    }
492
493
    /**
494
     * @return Decimal the absolute value of this number
495
     */
496
    public function abs(): Decimal
497
    {
498
        if ($this->isNegative()) {
499
            return $this->negate();
500
        } else {
501
            return $this;
502
        }
503
    }
504
505
    /**
506
     * @return Decimal a new decimal number, representing the negative value of this number
507
     */
508
    public function negate(): Decimal
509
    {
510
        if ($this->isNaN() || $this->isZero()) {
511
            return $this;
512
        } elseif ($this->val[0] == '-') {
513
            return new Decimal(substr($this->val, 1));
514
        } else {
515
            return new Decimal('-' . $this->val);
516
        }
517
    }
518
519
    /**
520
     * Computes the exact factorial of this number.
521
     * Only defined on numbers of scale zero, i.e., those without decimal part, and on the not-a-number.
522
     *
523
     * In conformance with PostgreSQL, the factorial of non-positive numbers is defined to be 1.
524
     *
525
     * @return Decimal the factorial of this number
526
     * @throws UndefinedOperationException if this number is neither a zero-scale number nor the not-a-number
527
     */
528
    public function factorial(): Decimal
529
    {
530
        if ($this->isNaN()) {
531
            return $this;
532
        }
533
        if ($this->scale > 0) {
534
            throw new UndefinedOperationException('Number with a decimal part');
535
        }
536
        if ($this->lessThan(2)) {
537
            return new Decimal('1');
538
        }
539
540
        if (System::hasGMP()) {
541
            return new Decimal(gmp_strval(gmp_fact($this->toGMP())));
542
        }
543
544
        // OPT: there are more efficient algorithms calculating the factorial; see, e.g., http://www.luschny.de/math/factorial/index.html
545
        $result = new Decimal('2');
546
        for ($i = new Decimal('3'); $i->compareTo($this) <= 0; $i = $i->add(1)) {
547
            $result = $result->multiply($i);
548
        }
549
        return $result;
550
    }
551
552
    /**
553
     * @param int $scale number of decimal places to round this number to; may be negative to round to higher orders
554
     * @return Decimal a new decimal number, representing this number rounded to <tt>$scale</tt> decimal places
555
     */
556
    public function round(int $scale = 0): Decimal
557
    {
558
        return new Decimal($this->val, $scale);
559
    }
560
561
    /**
562
     * @return Decimal largest integer not greater than this number
563
     */
564
    public function floor(): Decimal
565
    {
566
        if ($this->isNaN()) {
567
            return $this;
568
        }
569
570
        $decPoint = strpos($this->val, '.');
571
        if ($decPoint === false) {
572
            return $this;
573
        }
574
575
        if ($this->val[0] != '-') {
576
            return new Decimal(substr($this->val, 0, $decPoint));
577
        } elseif ($this->isInteger()) {
578
            return new Decimal($this->val, 0);
579
        } else {
580
            // negative number with non-zero fractional part
581
            return new Decimal(bcsub(substr($this->val, 0, $decPoint), '1', 0));
582
        }
583
    }
584
585
    /**
586
     * @return Decimal smallest integer not less than this number
587
     */
588
    public function ceil(): Decimal
589
    {
590
        if ($this->isNaN()) {
591
            return $this;
592
        }
593
594
        $decPoint = strpos($this->val, '.');
595
        if ($decPoint === false) {
596
            return $this;
597
        }
598
599
        if ($this->val[0] == '-') {
600
            return new Decimal(substr($this->val, 0, $decPoint));
601
        } elseif ($this->isInteger()) {
602
            return new Decimal($this->val, 0);
603
        } else {
604
            // non-negative number with non-zero fractional part
605
            return new Decimal(bcadd(substr($this->val, 0, $decPoint), '1', 0));
606
        }
607
    }
608
609
    /**
610
     * @param string|int|float|Decimal|object $number
611
     * @return Decimal the greater of this number and <tt>$number</tt>, preferably this number if numerically equal
612
     */
613
    public function max($number): Decimal
614
    {
615
        $arg = self::fromNumber($number);
616
        if ($this->lessThan($arg)) {
617
            return $arg;
618
        } else {
619
            return $this;
620
        }
621
    }
622
623
    /**
624
     * @param string|int|float|Decimal|object $number
625
     * @return Decimal the smaller of this number and <tt>$number</tt>, preferably this number if numerically equal
626
     */
627
    public function min($number): Decimal
628
    {
629
        $arg = self::fromNumber($number);
630
        if ($this->greaterThan($arg)) {
631
            return $arg;
632
        } else {
633
            return $this;
634
        }
635
    }
636
637
    /**
638
     * @return Decimal a new decimal number, representing the square root of this number
639
     * @throws UndefinedOperationException if this is a negative number
640
     */
641
    public function sqrt(): Decimal
642
    {
643
        if ($this->isNaN()) {
644
            return $this;
645
        }
646
        if ($this->isNegative()) {
647
            throw new UndefinedOperationException('square root of negative number');
648
        }
649
        $scale = max($this->scale, self::MIN_SIG_DIGITS);
650
        return new Decimal(bcsqrt($this->val, $scale + 1));
651
    }
652
653
654
    /**
655
     * @return int|null the value of this number cast explicitly to <tt>int</tt>;
656
     *                  for big numbers, this yields the maximal available integer value (i.e., <tt>PHP_INT_MAX</tt> or
657
     *                    <tt>PHP_INT_MIN</tt>);
658
     *                  <tt>null</tt> is returned if this is the not-a-number
659
     */
660
    public function toInt(): ?int
661
    {
662
        return ($this->val === null ? null : (int)$this->val);
663
    }
664
665
    /**
666
     * @return float the value of this number cast explicitly to <tt>float</tt>, or the float <tt>NAN</tt> value if this
667
     *                 is the not-a-number
668
     */
669
    public function toFloat(): float
670
    {
671
        return ($this->val === null ? NAN : (float)$this->val);
672
    }
673
674
    /**
675
     * @return string the value of this number, or the special string <tt>'NaN'</tt> for the not-a-number value
676
     */
677
    public function toString(): string
678
    {
679
        return ($this->val === null ? 'NaN' : $this->val);
680
    }
681
682
    /**
683
     * Converts the value to a {@link \GMP} object.
684
     *
685
     * Requires the `gmp` PHP extension.
686
     *
687
     * @return \GMP a {@link \GMP} object representing the same integer as this number
688
     * @throws UndefinedOperationException if this number is not an integer, i.e., has some non-zero fractional digits
689
     */
690
    public function toGMP()
691
    {
692
        if (!$this->isInteger()) {
693
            throw new UndefinedOperationException('toGMP() is only defined for integers');
694
        }
695
        $decPoint = strpos($this->val, '.');
696
        $str = ($decPoint === false ? $this->val : substr($this->val, 0, $decPoint - 1));
697
        return gmp_init($str);
698
    }
699
700
    public function __toString()
701
    {
702
        return $this->toString();
703
    }
704
}
705