• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

brick / math / 21805561332

08 Feb 2026 09:19PM UTC coverage: 99.191%. Remained the same
21805561332

push

github

BenMorel
Remove deprecated BigRational::simplified()

1349 of 1360 relevant lines covered (99.19%)

2293.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.57
/src/BigDecimal.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Brick\Math;
6

7
use Brick\Math\Exception\DivisionByZeroException;
8
use Brick\Math\Exception\InvalidArgumentException;
9
use Brick\Math\Exception\MathException;
10
use Brick\Math\Exception\NegativeNumberException;
11
use Brick\Math\Exception\RoundingNecessaryException;
12
use Brick\Math\Internal\CalculatorRegistry;
13
use Brick\Math\Internal\DecimalHelper;
14
use LogicException;
15
use Override;
16

17
use function in_array;
18
use function intdiv;
19
use function max;
20
use function rtrim;
21
use function str_repeat;
22
use function strlen;
23
use function substr;
24
use function trigger_error;
25

26
use const E_USER_DEPRECATED;
27

28
/**
29
 * An arbitrarily large decimal number.
30
 *
31
 * This class is immutable.
32
 *
33
 * The scale of the number is the number of digits after the decimal point. It is always positive or zero.
34
 */
35
final readonly class BigDecimal extends BigNumber
36
{
37
    /**
38
     * The unscaled value of this decimal number.
39
     *
40
     * This is a string of digits with an optional leading minus sign.
41
     * No leading zero must be present.
42
     * No leading minus sign must be present if the value is 0.
43
     */
44
    private string $value;
45

46
    /**
47
     * The scale (number of digits after the decimal point) of this decimal number.
48
     *
49
     * This must be zero or more.
50
     */
51
    private int $scale;
52

53
    /**
54
     * Protected constructor. Use a factory method to obtain an instance.
55
     *
56
     * @param string $value The unscaled value, validated.
57
     * @param int    $scale The scale, validated.
58
     *
59
     * @pure
60
     */
61
    protected function __construct(string $value, int $scale = 0)
62
    {
63
        $this->value = $value;
12,411✔
64
        $this->scale = $scale;
12,411✔
65
    }
66

67
    /**
68
     * Creates a BigDecimal from an unscaled value and a scale.
69
     *
70
     * Example: `(12345, 3)` will result in the BigDecimal `12.345`.
71
     *
72
     * A negative scale is normalized to zero by appending zeros to the unscaled value.
73
     *
74
     * Example: `(12345, -3)` will result in the BigDecimal `12345000`.
75
     *
76
     * @param BigNumber|int|string $value The unscaled value. Must be convertible to a BigInteger.
77
     * @param int                  $scale The scale of the number. If negative, the scale will be set to zero
78
     *                                    and the unscaled value will be adjusted accordingly.
79
     *
80
     * @throws MathException If the value is not valid, or is not convertible to a BigInteger.
81
     *
82
     * @pure
83
     */
84
    public static function ofUnscaledValue(BigNumber|int|string $value, int $scale = 0): BigDecimal
85
    {
86
        $value = BigInteger::of($value)->toString();
279✔
87

88
        if ($scale < 0) {
279✔
89
            if ($value !== '0') {
66✔
90
                $value .= str_repeat('0', -$scale);
54✔
91
            }
92
            $scale = 0;
66✔
93
        }
94

95
        return new BigDecimal($value, $scale);
279✔
96
    }
97

98
    /**
99
     * Returns a BigDecimal representing zero, with a scale of zero.
100
     *
101
     * @pure
102
     */
103
    public static function zero(): BigDecimal
104
    {
105
        /** @var BigDecimal|null $zero */
106
        static $zero;
30✔
107

108
        if ($zero === null) {
30✔
109
            $zero = new BigDecimal('0');
3✔
110
        }
111

112
        return $zero;
30✔
113
    }
114

115
    /**
116
     * Returns a BigDecimal representing one, with a scale of zero.
117
     *
118
     * @pure
119
     */
120
    public static function one(): BigDecimal
121
    {
122
        /** @var BigDecimal|null $one */
123
        static $one;
30✔
124

125
        if ($one === null) {
30✔
126
            $one = new BigDecimal('1');
3✔
127
        }
128

129
        return $one;
30✔
130
    }
131

132
    /**
133
     * Returns a BigDecimal representing ten, with a scale of zero.
134
     *
135
     * @pure
136
     */
137
    public static function ten(): BigDecimal
138
    {
139
        /** @var BigDecimal|null $ten */
140
        static $ten;
3✔
141

142
        if ($ten === null) {
3✔
143
            $ten = new BigDecimal('10');
3✔
144
        }
145

146
        return $ten;
3✔
147
    }
148

149
    /**
150
     * Returns the sum of this number and the given one.
151
     *
152
     * The result has a scale of `max($this->scale, $that->scale)`.
153
     *
154
     * @param BigNumber|int|string $that The number to add. Must be convertible to a BigDecimal.
155
     *
156
     * @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
157
     *
158
     * @pure
159
     */
160
    public function plus(BigNumber|int|string $that): BigDecimal
161
    {
162
        $that = BigDecimal::of($that);
264✔
163

164
        if ($that->value === '0' && $that->scale <= $this->scale) {
264✔
165
            return $this;
24✔
166
        }
167

168
        if ($this->value === '0' && $this->scale <= $that->scale) {
240✔
169
            return $that;
48✔
170
        }
171

172
        [$a, $b] = $this->scaleValues($this, $that);
216✔
173

174
        $value = CalculatorRegistry::get()->add($a, $b);
216✔
175
        $scale = $this->scale > $that->scale ? $this->scale : $that->scale;
216✔
176

177
        return new BigDecimal($value, $scale);
216✔
178
    }
179

180
    /**
181
     * Returns the difference of this number and the given one.
182
     *
183
     * The result has a scale of `max($this->scale, $that->scale)`.
184
     *
185
     * @param BigNumber|int|string $that The number to subtract. Must be convertible to a BigDecimal.
186
     *
187
     * @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
188
     *
189
     * @pure
190
     */
191
    public function minus(BigNumber|int|string $that): BigDecimal
192
    {
193
        $that = BigDecimal::of($that);
180✔
194

195
        if ($that->value === '0' && $that->scale <= $this->scale) {
180✔
196
            return $this;
15✔
197
        }
198

199
        [$a, $b] = $this->scaleValues($this, $that);
165✔
200

201
        $value = CalculatorRegistry::get()->sub($a, $b);
165✔
202
        $scale = $this->scale > $that->scale ? $this->scale : $that->scale;
165✔
203

204
        return new BigDecimal($value, $scale);
165✔
205
    }
206

207
    /**
208
     * Returns the product of this number and the given one.
209
     *
210
     * The result has a scale of `$this->scale + $that->scale`.
211
     *
212
     * @param BigNumber|int|string $that The multiplier. Must be convertible to a BigDecimal.
213
     *
214
     * @throws MathException If the multiplier is not a valid number, or is not convertible to a BigDecimal.
215
     *
216
     * @pure
217
     */
218
    public function multipliedBy(BigNumber|int|string $that): BigDecimal
219
    {
220
        $that = BigDecimal::of($that);
246✔
221

222
        if ($that->value === '1' && $that->scale === 0) {
246✔
223
            return $this;
15✔
224
        }
225

226
        if ($this->value === '1' && $this->scale === 0) {
231✔
227
            return $that;
9✔
228
        }
229

230
        $value = CalculatorRegistry::get()->mul($this->value, $that->value);
222✔
231
        $scale = $this->scale + $that->scale;
222✔
232

233
        return new BigDecimal($value, $scale);
222✔
234
    }
235

236
    /**
237
     * Returns the result of the division of this number by the given one, at the given scale.
238
     *
239
     * @param BigNumber|int|string $that         The divisor. Must be convertible to a BigDecimal.
240
     * @param int                  $scale        The desired scale. Must be non-negative.
241
     * @param RoundingMode         $roundingMode An optional rounding mode, defaults to Unnecessary.
242
     *
243
     * @throws InvalidArgumentException   If the scale is negative.
244
     * @throws MathException              If the divisor is not a valid number or is not convertible to a BigDecimal.
245
     * @throws DivisionByZeroException    If the divisor is zero.
246
     * @throws RoundingNecessaryException If RoundingMode::Unnecessary is used and the result cannot be represented exactly at the given scale.
247
     *
248
     * @pure
249
     */
250
    public function dividedBy(BigNumber|int|string $that, int $scale, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigDecimal
251
    {
252
        if ($scale < 0) {
1,905✔
253
            throw InvalidArgumentException::negativeScale();
3✔
254
        }
255

256
        $that = BigDecimal::of($that);
1,902✔
257

258
        if ($that->isZero()) {
1,902✔
259
            throw DivisionByZeroException::divisionByZero();
12✔
260
        }
261

262
        if ($that->value === '1' && $that->scale === 0 && $scale === $this->scale) {
1,890✔
263
            return $this;
114✔
264
        }
265

266
        $p = $this->valueWithMinScale($that->scale + $scale);
1,776✔
267
        $q = $that->valueWithMinScale($this->scale - $scale);
1,776✔
268

269
        $calculator = CalculatorRegistry::get();
1,776✔
270
        $result = $calculator->divRound($p, $q, $roundingMode);
1,776✔
271

272
        if ($result === null) {
1,776✔
273
            [$a, $b] = $this->scaleValues($this->abs(), $that->abs());
129✔
274

275
            $denominator = $calculator->divQ($b, $calculator->gcd($a, $b));
129✔
276
            $requiredScale = DecimalHelper::computeScaleFromReducedFractionDenominator($denominator);
129✔
277

278
            if ($requiredScale === null) {
129✔
279
                throw RoundingNecessaryException::decimalDivisionNotExact();
9✔
280
            }
281

282
            throw RoundingNecessaryException::decimalDivisionScaleTooSmall();
120✔
283
        }
284

285
        return new BigDecimal($result, $scale);
1,647✔
286
    }
287

288
    /**
289
     * Returns the exact result of the division of this number by the given one.
290
     *
291
     * The scale of the result is automatically calculated to fit all the fraction digits.
292
     *
293
     * @param BigNumber|int|string $that The divisor. Must be convertible to a BigDecimal.
294
     *
295
     * @throws MathException              If the divisor is not a valid number or is not convertible to a BigDecimal.
296
     * @throws DivisionByZeroException    If the divisor is zero.
297
     * @throws RoundingNecessaryException If the result yields an infinite number of digits.
298
     *
299
     * @pure
300
     */
301
    public function dividedByExact(BigNumber|int|string $that): BigDecimal
302
    {
303
        $that = BigDecimal::of($that);
93✔
304

305
        if ($that->value === '0') {
93✔
306
            throw DivisionByZeroException::divisionByZero();
9✔
307
        }
308

309
        [$a, $b] = $this->scaleValues($this->abs(), $that->abs());
84✔
310

311
        $calculator = CalculatorRegistry::get();
84✔
312

313
        $denominator = $calculator->divQ($b, $calculator->gcd($a, $b));
84✔
314
        $scale = DecimalHelper::computeScaleFromReducedFractionDenominator($denominator);
84✔
315

316
        if ($scale === null) {
84✔
317
            throw RoundingNecessaryException::decimalDivisionNotExact();
12✔
318
        }
319

320
        return $this->dividedBy($that, $scale)->strippedOfTrailingZeros();
72✔
321
    }
322

323
    /**
324
     * Returns this number exponentiated to the given value.
325
     *
326
     * The result has a scale of `$this->scale * $exponent`.
327
     *
328
     * @throws InvalidArgumentException If the exponent is negative.
329
     *
330
     * @pure
331
     */
332
    public function power(int $exponent): BigDecimal
333
    {
334
        if ($exponent === 0) {
117✔
335
            return BigDecimal::one();
27✔
336
        }
337

338
        if ($exponent === 1) {
90✔
339
            return $this;
21✔
340
        }
341

342
        if ($exponent < 0) {
69✔
343
            throw InvalidArgumentException::negativeExponent();
3✔
344
        }
345

346
        return new BigDecimal(CalculatorRegistry::get()->pow($this->value, $exponent), $this->scale * $exponent);
66✔
347
    }
348

349
    /**
350
     * Returns the quotient of the division of this number by the given one.
351
     *
352
     * The quotient has a scale of `0`.
353
     *
354
     * Examples:
355
     *
356
     * - `7.5` quotient `3` returns `2`
357
     * - `7.5` quotient `-3` returns `-2`
358
     * - `-7.5` quotient `3` returns `-2`
359
     * - `-7.5` quotient `-3` returns `2`
360
     *
361
     * @param BigNumber|int|string $that The divisor. Must be convertible to a BigDecimal.
362
     *
363
     * @throws MathException           If the divisor is not valid, or is not convertible to a BigDecimal.
364
     * @throws DivisionByZeroException If the divisor is zero.
365
     *
366
     * @pure
367
     */
368
    public function quotient(BigNumber|int|string $that): BigDecimal
369
    {
370
        $that = BigDecimal::of($that);
159✔
371

372
        if ($that->isZero()) {
159✔
373
            throw DivisionByZeroException::divisionByZero();
3✔
374
        }
375

376
        $p = $this->valueWithMinScale($that->scale);
156✔
377
        $q = $that->valueWithMinScale($this->scale);
156✔
378

379
        $quotient = CalculatorRegistry::get()->divQ($p, $q);
156✔
380

381
        return new BigDecimal($quotient, 0);
156✔
382
    }
383

384
    /**
385
     * Returns the remainder of the division of this number by the given one.
386
     *
387
     * The remainder has a scale of `max($this->scale, $that->scale)`.
388
     * The remainder, when non-zero, has the same sign as the dividend.
389
     *
390
     * Examples:
391
     *
392
     * - `7.5` remainder `3` returns `1.5`
393
     * - `7.5` remainder `-3` returns `1.5`
394
     * - `-7.5` remainder `3` returns `-1.5`
395
     * - `-7.5` remainder `-3` returns `-1.5`
396
     *
397
     * @param BigNumber|int|string $that The divisor. Must be convertible to a BigDecimal.
398
     *
399
     * @throws MathException           If the divisor is not valid, or is not convertible to a BigDecimal.
400
     * @throws DivisionByZeroException If the divisor is zero.
401
     *
402
     * @pure
403
     */
404
    public function remainder(BigNumber|int|string $that): BigDecimal
405
    {
406
        $that = BigDecimal::of($that);
159✔
407

408
        if ($that->isZero()) {
159✔
409
            throw DivisionByZeroException::divisionByZero();
3✔
410
        }
411

412
        $p = $this->valueWithMinScale($that->scale);
156✔
413
        $q = $that->valueWithMinScale($this->scale);
156✔
414

415
        $remainder = CalculatorRegistry::get()->divR($p, $q);
156✔
416

417
        $scale = $this->scale > $that->scale ? $this->scale : $that->scale;
156✔
418

419
        return new BigDecimal($remainder, $scale);
156✔
420
    }
421

422
    /**
423
     * Returns the quotient and remainder of the division of this number by the given one.
424
     *
425
     * The quotient has a scale of `0`, and the remainder has a scale of `max($this->scale, $that->scale)`.
426
     *
427
     * Examples:
428
     *
429
     * - `7.5` quotientAndRemainder `3` returns [`2`, `1.5`]
430
     * - `7.5` quotientAndRemainder `-3` returns [`-2`, `1.5`]
431
     * - `-7.5` quotientAndRemainder `3` returns [`-2`, `-1.5`]
432
     * - `-7.5` quotientAndRemainder `-3` returns [`2`, `-1.5`]
433
     *
434
     * @param BigNumber|int|string $that The divisor. Must be convertible to a BigDecimal.
435
     *
436
     * @return array{BigDecimal, BigDecimal} An array containing the quotient and the remainder.
437
     *
438
     * @throws MathException           If the divisor is not valid, or is not convertible to a BigDecimal.
439
     * @throws DivisionByZeroException If the divisor is zero.
440
     *
441
     * @pure
442
     */
443
    public function quotientAndRemainder(BigNumber|int|string $that): array
444
    {
445
        $that = BigDecimal::of($that);
159✔
446

447
        if ($that->isZero()) {
159✔
448
            throw DivisionByZeroException::divisionByZero();
3✔
449
        }
450

451
        $p = $this->valueWithMinScale($that->scale);
156✔
452
        $q = $that->valueWithMinScale($this->scale);
156✔
453

454
        [$quotient, $remainder] = CalculatorRegistry::get()->divQR($p, $q);
156✔
455

456
        $scale = $this->scale > $that->scale ? $this->scale : $that->scale;
156✔
457

458
        $quotient = new BigDecimal($quotient, 0);
156✔
459
        $remainder = new BigDecimal($remainder, $scale);
156✔
460

461
        return [$quotient, $remainder];
156✔
462
    }
463

464
    /**
465
     * Returns the square root of this number, rounded to the given scale according to the given rounding mode.
466
     *
467
     * @param int          $scale        The target scale. Must be non-negative.
468
     * @param RoundingMode $roundingMode An optional rounding mode, defaults to Unnecessary.
469
     *
470
     * @throws InvalidArgumentException   If the scale is negative.
471
     * @throws NegativeNumberException    If this number is negative.
472
     * @throws RoundingNecessaryException If RoundingMode::Unnecessary is used, but rounding is necessary.
473
     *
474
     * @pure
475
     */
476
    public function sqrt(int $scale, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigDecimal
477
    {
478
        if ($scale < 0) {
6,150✔
479
            throw InvalidArgumentException::negativeScale();
3✔
480
        }
481

482
        if ($this->value === '0') {
6,147✔
483
            return new BigDecimal('0', $scale);
120✔
484
        }
485

486
        if ($this->value[0] === '-') {
6,027✔
487
            throw NegativeNumberException::squareRootOfNegativeNumber();
3✔
488
        }
489

490
        $value = $this->value;
6,024✔
491
        $inputScale = $this->scale;
6,024✔
492

493
        if ($inputScale % 2 !== 0) {
6,024✔
494
            $value .= '0';
2,346✔
495
            $inputScale++;
2,346✔
496
        }
497

498
        $calculator = CalculatorRegistry::get();
6,024✔
499

500
        // Keep one extra digit for rounding.
501
        $intermediateScale = max($scale, intdiv($inputScale, 2)) + 1;
6,024✔
502
        $value .= str_repeat('0', 2 * $intermediateScale - $inputScale);
6,024✔
503

504
        $sqrt = $calculator->sqrt($value);
6,024✔
505
        $isExact = $calculator->mul($sqrt, $sqrt) === $value;
6,024✔
506

507
        if (! $isExact) {
6,024✔
508
            if ($roundingMode === RoundingMode::Unnecessary) {
3,774✔
509
                throw RoundingNecessaryException::decimalSquareRootNotExact();
375✔
510
            }
511

512
            // Non-perfect-square sqrt is irrational, so the true value is strictly above this sqrt floor.
513
            // Add one at the intermediate scale to guarantee Up/Ceiling round up at the target scale.
514
            if (in_array($roundingMode, [RoundingMode::Up, RoundingMode::Ceiling], true)) {
3,399✔
515
                $sqrt = $calculator->add($sqrt, '1');
774✔
516
            }
517

518
            // Irrational sqrt cannot land exactly on a midpoint; treat tie-to-down modes as HalfUp.
519
            elseif (in_array($roundingMode, [RoundingMode::HalfDown, RoundingMode::HalfEven, RoundingMode::HalfFloor], true)) {
2,625✔
520
                $roundingMode = RoundingMode::HalfUp;
1,125✔
521
            }
522
        }
523

524
        $scaled = DecimalHelper::scale($sqrt, $intermediateScale, $scale, $roundingMode);
5,649✔
525

526
        if ($scaled === null) {
5,649✔
527
            throw RoundingNecessaryException::decimalSquareRootScaleTooSmall();
42✔
528
        }
529

530
        return new BigDecimal($scaled, $scale);
5,607✔
531
    }
532

533
    /**
534
     * Returns a copy of this BigDecimal with the decimal point moved to the left by the given number of places.
535
     *
536
     * @pure
537
     */
538
    public function withPointMovedLeft(int $places): BigDecimal
539
    {
540
        if ($places === 0) {
231✔
541
            return $this;
33✔
542
        }
543

544
        if ($places < 0) {
198✔
545
            return $this->withPointMovedRight(-$places);
66✔
546
        }
547

548
        return new BigDecimal($this->value, $this->scale + $places);
132✔
549
    }
550

551
    /**
552
     * Returns a copy of this BigDecimal with the decimal point moved to the right by the given number of places.
553
     *
554
     * @pure
555
     */
556
    public function withPointMovedRight(int $places): BigDecimal
557
    {
558
        if ($places === 0) {
231✔
559
            return $this;
33✔
560
        }
561

562
        if ($places < 0) {
198✔
563
            return $this->withPointMovedLeft(-$places);
66✔
564
        }
565

566
        $value = $this->value;
132✔
567
        $scale = $this->scale - $places;
132✔
568

569
        if ($scale < 0) {
132✔
570
            if ($value !== '0') {
78✔
571
                $value .= str_repeat('0', -$scale);
60✔
572
            }
573
            $scale = 0;
78✔
574
        }
575

576
        return new BigDecimal($value, $scale);
132✔
577
    }
578

579
    /**
580
     * Returns a copy of this BigDecimal with any trailing zeros removed from the fractional part.
581
     *
582
     * @pure
583
     */
584
    public function strippedOfTrailingZeros(): BigDecimal
585
    {
586
        if ($this->scale === 0) {
453✔
587
            return $this;
114✔
588
        }
589

590
        $trimmedValue = rtrim($this->value, '0');
339✔
591

592
        if ($trimmedValue === '') {
339✔
593
            return BigDecimal::zero();
9✔
594
        }
595

596
        $trimmableZeros = strlen($this->value) - strlen($trimmedValue);
330✔
597

598
        if ($trimmableZeros === 0) {
330✔
599
            return $this;
282✔
600
        }
601

602
        if ($trimmableZeros > $this->scale) {
48✔
603
            $trimmableZeros = $this->scale;
12✔
604
        }
605

606
        $value = substr($this->value, 0, -$trimmableZeros);
48✔
607
        $scale = $this->scale - $trimmableZeros;
48✔
608

609
        return new BigDecimal($value, $scale);
48✔
610
    }
611

612
    #[Override]
613
    public function negated(): static
614
    {
615
        return new BigDecimal(CalculatorRegistry::get()->neg($this->value), $this->scale);
1,401✔
616
    }
617

618
    #[Override]
619
    public function compareTo(BigNumber|int|string $that): int
620
    {
621
        $that = BigNumber::of($that);
858✔
622

623
        if ($that instanceof BigInteger) {
858✔
624
            $that = $that->toBigDecimal();
504✔
625
        }
626

627
        if ($that instanceof BigDecimal) {
858✔
628
            [$a, $b] = $this->scaleValues($this, $that);
789✔
629

630
            return CalculatorRegistry::get()->cmp($a, $b);
789✔
631
        }
632

633
        return -$that->compareTo($this);
69✔
634
    }
635

636
    #[Override]
637
    public function getSign(): int
638
    {
639
        return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1);
2,523✔
640
    }
641

642
    /**
643
     * @pure
644
     */
645
    public function getUnscaledValue(): BigInteger
646
    {
647
        return self::newBigInteger($this->value);
2,892✔
648
    }
649

650
    /**
651
     * @pure
652
     */
653
    public function getScale(): int
654
    {
655
        return $this->scale;
2,892✔
656
    }
657

658
    /**
659
     * Returns the number of significant digits in the number.
660
     *
661
     * This is the number of digits in the unscaled value of the number.
662
     * The sign has no impact on the result.
663
     *
664
     * Examples:
665
     *   0 => 1
666
     *   0.0 => 1
667
     *   123 => 3
668
     *   123.456 => 6
669
     *   0.00123 => 3
670
     *   0.0012300 => 5
671
     *
672
     * @pure
673
     */
674
    public function getPrecision(): int
675
    {
676
        $length = strlen($this->value);
81✔
677

678
        return ($this->value[0] === '-') ? $length - 1 : $length;
81✔
679
    }
680

681
    /**
682
     * Returns whether this decimal number has a non-zero fractional part.
683
     *
684
     * @deprecated Will be removed in 0.17. Use `! $number->getFractionalPart()->isZero()` instead.
685
     */
686
    public function hasNonZeroFractionalPart(): bool
687
    {
688
        trigger_error(
18✔
689
            'BigDecimal::hasNonZeroFractionalPart() is deprecated and will be removed in 0.17. Use `! $number->getFractionalPart()->isZero()` instead.',
18✔
690
            E_USER_DEPRECATED,
18✔
691
        );
18✔
692

693
        return ! $this->getFractionalPart()->isZero();
18✔
694
    }
695

696
    /**
697
     * Returns the integral part of this decimal number.
698
     *
699
     * Examples:
700
     *
701
     * - `123.456` returns `123`
702
     * - `-123.456` returns `-123`
703
     * - `0.123` returns `0`
704
     * - `-0.123` returns `0`
705
     *
706
     * The following identity holds: `$d->isEqualTo($d->getFractionalPart()->plus($d->getIntegralPart()))`.
707
     *
708
     * @pure
709
     */
710
    public function getIntegralPart(): BigInteger
711
    {
712
        if ($this->scale === 0) {
54✔
713
            return self::newBigInteger($this->value);
9✔
714
        }
715

716
        $value = DecimalHelper::padUnscaledValue($this->value, $this->scale);
45✔
717
        $integerPart = substr($value, 0, -$this->scale);
45✔
718

719
        if ($integerPart === '-0') {
45✔
720
            $integerPart = '0';
3✔
721
        }
722

723
        return self::newBigInteger($integerPart);
45✔
724
    }
725

726
    /**
727
     * Returns the fractional part of this decimal number.
728
     *
729
     * Examples:
730
     *
731
     * - `123.456` returns `0.456`
732
     * - `-123.456` returns `-0.456`
733
     * - `123` returns `0`
734
     * - `-123` returns `0`
735
     *
736
     * The following identity holds: `$d->isEqualTo($d->getFractionalPart()->plus($d->getIntegralPart()))`.
737
     *
738
     * @pure
739
     */
740
    public function getFractionalPart(): BigDecimal
741
    {
742
        if ($this->scale === 0) {
60✔
743
            return BigDecimal::zero();
15✔
744
        }
745

746
        return $this->minus($this->getIntegralPart());
45✔
747
    }
748

749
    #[Override]
750
    public function toBigInteger(): BigInteger
751
    {
752
        if ($this->scale === 0) {
252✔
753
            return self::newBigInteger($this->value);
135✔
754
        }
755

756
        $rational = $this->toBigRational();
138✔
757
        $integralPart = $rational->getIntegralPart();
138✔
758

759
        if ($rational->isEqualTo($integralPart)) {
138✔
760
            return $integralPart;
87✔
761
        }
762

763
        throw RoundingNecessaryException::decimalNotConvertibleToInteger();
51✔
764
    }
765

766
    #[Override]
767
    public function toBigDecimal(): BigDecimal
768
    {
769
        return $this;
8,787✔
770
    }
771

772
    #[Override]
773
    public function toBigRational(): BigRational
774
    {
775
        $numerator = self::newBigInteger($this->value);
495✔
776
        $denominator = self::newBigInteger('1' . str_repeat('0', $this->scale));
495✔
777

778
        return self::newBigRational($numerator, $denominator, false, true);
495✔
779
    }
780

781
    #[Override]
782
    public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigDecimal
783
    {
784
        if ($scale < 0) {
75✔
785
            throw InvalidArgumentException::negativeScale();
×
786
        }
787

788
        if ($scale === $this->scale) {
75✔
789
            return $this;
15✔
790
        }
791

792
        $value = DecimalHelper::scale($this->value, $this->scale, $scale, $roundingMode);
60✔
793

794
        if ($value === null) {
60✔
795
            throw RoundingNecessaryException::decimalScaleTooSmall();
9✔
796
        }
797

798
        return new BigDecimal($value, $scale);
51✔
799
    }
800

801
    #[Override]
802
    public function toInt(): int
803
    {
804
        return $this->toBigInteger()->toInt();
33✔
805
    }
806

807
    #[Override]
808
    public function toFloat(): float
809
    {
810
        return (float) $this->toString();
117✔
811
    }
812

813
    /**
814
     * @return numeric-string
815
     */
816
    #[Override]
817
    public function toString(): string
818
    {
819
        if ($this->scale === 0) {
7,203✔
820
            /** @var numeric-string */
821
            return $this->value;
1,593✔
822
        }
823

824
        $value = DecimalHelper::padUnscaledValue($this->value, $this->scale);
5,655✔
825

826
        /** @phpstan-ignore return.type */
827
        return substr($value, 0, -$this->scale) . '.' . substr($value, -$this->scale);
5,655✔
828
    }
829

830
    /**
831
     * This method is required for serializing the object and SHOULD NOT be accessed directly.
832
     *
833
     * @internal
834
     *
835
     * @return array{value: string, scale: int}
836
     */
837
    public function __serialize(): array
838
    {
839
        return ['value' => $this->value, 'scale' => $this->scale];
3✔
840
    }
841

842
    /**
843
     * This method is only here to allow unserializing the object and cannot be accessed directly.
844
     *
845
     * @internal
846
     *
847
     * @param array{value: string, scale: int} $data
848
     *
849
     * @throws LogicException
850
     */
851
    public function __unserialize(array $data): void
852
    {
853
        /** @phpstan-ignore isset.initializedProperty */
854
        if (isset($this->value)) {
6✔
855
            throw new LogicException('__unserialize() is an internal function, it must not be called directly.');
3✔
856
        }
857

858
        /** @phpstan-ignore deadCode.unreachable */
859
        $this->value = $data['value'];
3✔
860
        $this->scale = $data['scale'];
3✔
861
    }
862

863
    #[Override]
864
    protected static function from(BigNumber $number): static
865
    {
866
        return $number->toBigDecimal();
11,682✔
867
    }
868

869
    /**
870
     * Puts the internal values of the given decimal numbers on the same scale.
871
     *
872
     * @return array{string, string} The scaled integer values of $x and $y.
873
     *
874
     * @pure
875
     */
876
    private function scaleValues(BigDecimal $x, BigDecimal $y): array
877
    {
878
        $a = $x->value;
1,335✔
879
        $b = $y->value;
1,335✔
880

881
        if ($b !== '0' && $x->scale > $y->scale) {
1,335✔
882
            $b .= str_repeat('0', $x->scale - $y->scale);
513✔
883
        } elseif ($a !== '0' && $x->scale < $y->scale) {
879✔
884
            $a .= str_repeat('0', $y->scale - $x->scale);
198✔
885
        }
886

887
        return [$a, $b];
1,335✔
888
    }
889

890
    /**
891
     * @pure
892
     */
893
    private function valueWithMinScale(int $scale): string
894
    {
895
        $value = $this->value;
1,932✔
896

897
        if ($this->value !== '0' && $scale > $this->scale) {
1,932✔
898
            $value .= str_repeat('0', $scale - $this->scale);
1,770✔
899
        }
900

901
        return $value;
1,932✔
902
    }
903
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc