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

brick / math / 21870665359

10 Feb 2026 03:16PM UTC coverage: 98.918% (-0.3%) from 99.191%
21870665359

push

github

BenMorel
Remove deprecated BigRational::simplified()

1371 of 1386 relevant lines covered (98.92%)

2305.18 hits per line

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

99.58
/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->isZero() && $that->scale <= $this->scale) {
264✔
165
            return $this;
24✔
166
        }
167

168
        if ($this->isZero() && $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 = max($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->isZero() && $that->scale <= $this->scale) {
180✔
196
            return $this;
15✔
197
        }
198

199
        if ($this->isZero() && $this->scale <= $that->scale) {
165✔
200
            return $that->negated();
9✔
201
        }
202

203
        [$a, $b] = $this->scaleValues($this, $that);
156✔
204

205
        $value = CalculatorRegistry::get()->sub($a, $b);
156✔
206
        $scale = max($this->scale, $that->scale);
156✔
207

208
        return new BigDecimal($value, $scale);
156✔
209
    }
210

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

226
        if ($that->isOneScaleZero()) {
246✔
227
            return $this;
15✔
228
        }
229

230
        if ($this->isOneScaleZero()) {
231✔
231
            return $that;
9✔
232
        }
233

234
        if ($that->isZero() || $this->isZero()) {
222✔
235
            return new BigDecimal('0', $this->scale + $that->scale);
54✔
236
        }
237

238
        $value = CalculatorRegistry::get()->mul($this->value, $that->value);
168✔
239
        $scale = $this->scale + $that->scale;
168✔
240

241
        return new BigDecimal($value, $scale);
168✔
242
    }
243

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

265
        $that = BigDecimal::of($that);
1,902✔
266

267
        if ($that->isZero()) {
1,902✔
268
            throw DivisionByZeroException::divisionByZero();
12✔
269
        }
270

271
        if ($that->isOneScaleZero() && $scale === $this->scale) {
1,890✔
272
            return $this;
114✔
273
        }
274

275
        $p = $this->valueWithMinScale($that->scale + $scale);
1,776✔
276
        $q = $that->valueWithMinScale($this->scale - $scale);
1,776✔
277

278
        $calculator = CalculatorRegistry::get();
1,776✔
279
        $result = $calculator->divRound($p, $q, $roundingMode);
1,776✔
280

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

284
            $denominator = $calculator->divQ($b, $calculator->gcd($a, $b));
129✔
285
            $requiredScale = DecimalHelper::computeScaleFromReducedFractionDenominator($denominator);
129✔
286

287
            if ($requiredScale === null) {
129✔
288
                throw RoundingNecessaryException::decimalDivisionNotExact();
9✔
289
            }
290

291
            throw RoundingNecessaryException::decimalDivisionScaleTooSmall();
120✔
292
        }
293

294
        return new BigDecimal($result, $scale);
1,647✔
295
    }
296

297
    /**
298
     * Returns the exact result of the division of this number by the given one.
299
     *
300
     * The scale of the result is automatically calculated to fit all the fraction digits.
301
     *
302
     * @param BigNumber|int|string $that The divisor. Must be convertible to a BigDecimal.
303
     *
304
     * @throws MathException              If the divisor is not valid, or is not convertible to a BigDecimal.
305
     * @throws DivisionByZeroException    If the divisor is zero.
306
     * @throws RoundingNecessaryException If the result yields an infinite number of digits.
307
     *
308
     * @pure
309
     */
310
    public function dividedByExact(BigNumber|int|string $that): BigDecimal
311
    {
312
        $that = BigDecimal::of($that);
93✔
313

314
        if ($that->isZero()) {
93✔
315
            throw DivisionByZeroException::divisionByZero();
9✔
316
        }
317

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

320
        $calculator = CalculatorRegistry::get();
84✔
321

322
        $denominator = $calculator->divQ($b, $calculator->gcd($a, $b));
84✔
323
        $scale = DecimalHelper::computeScaleFromReducedFractionDenominator($denominator);
84✔
324

325
        if ($scale === null) {
84✔
326
            throw RoundingNecessaryException::decimalDivisionNotExact();
12✔
327
        }
328

329
        return $this->dividedBy($that, $scale)->strippedOfTrailingZeros();
72✔
330
    }
331

332
    /**
333
     * Returns this number exponentiated to the given value.
334
     *
335
     * The result has a scale of `$this->scale * $exponent`.
336
     *
337
     * @throws InvalidArgumentException If the exponent is negative.
338
     *
339
     * @pure
340
     */
341
    public function power(int $exponent): BigDecimal
342
    {
343
        if ($exponent === 0) {
117✔
344
            return BigDecimal::one();
27✔
345
        }
346

347
        if ($exponent === 1) {
90✔
348
            return $this;
21✔
349
        }
350

351
        if ($exponent < 0) {
69✔
352
            throw InvalidArgumentException::negativeExponent();
3✔
353
        }
354

355
        return new BigDecimal(CalculatorRegistry::get()->pow($this->value, $exponent), $this->scale * $exponent);
66✔
356
    }
357

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

381
        if ($that->isZero()) {
159✔
382
            throw DivisionByZeroException::divisionByZero();
3✔
383
        }
384

385
        $p = $this->valueWithMinScale($that->scale);
156✔
386
        $q = $that->valueWithMinScale($this->scale);
156✔
387

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

390
        return new BigDecimal($quotient, 0);
156✔
391
    }
392

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

417
        if ($that->isZero()) {
159✔
418
            throw DivisionByZeroException::divisionByZero();
3✔
419
        }
420

421
        $p = $this->valueWithMinScale($that->scale);
156✔
422
        $q = $that->valueWithMinScale($this->scale);
156✔
423

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

426
        $scale = max($this->scale, $that->scale);
156✔
427

428
        return new BigDecimal($remainder, $scale);
156✔
429
    }
430

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

456
        if ($that->isZero()) {
159✔
457
            throw DivisionByZeroException::divisionByZero();
3✔
458
        }
459

460
        $p = $this->valueWithMinScale($that->scale);
156✔
461
        $q = $that->valueWithMinScale($this->scale);
156✔
462

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

465
        $scale = max($this->scale, $that->scale);
156✔
466

467
        $quotient = new BigDecimal($quotient, 0);
156✔
468
        $remainder = new BigDecimal($remainder, $scale);
156✔
469

470
        return [$quotient, $remainder];
156✔
471
    }
472

473
    /**
474
     * Returns the square root of this number, rounded to the given scale according to the given rounding mode.
475
     *
476
     * @param int          $scale        The target scale. Must be non-negative.
477
     * @param RoundingMode $roundingMode An optional rounding mode, defaults to Unnecessary.
478
     *
479
     * @throws InvalidArgumentException   If the scale is negative.
480
     * @throws NegativeNumberException    If this number is negative.
481
     * @throws RoundingNecessaryException If RoundingMode::Unnecessary is used and the result cannot be represented
482
     *                                    exactly at the given scale.
483
     *
484
     * @pure
485
     */
486
    public function sqrt(int $scale, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigDecimal
487
    {
488
        if ($scale < 0) {
6,150✔
489
            throw InvalidArgumentException::negativeScale();
3✔
490
        }
491

492
        if ($this->isZero()) {
6,147✔
493
            return new BigDecimal('0', $scale);
120✔
494
        }
495

496
        if ($this->isNegative()) {
6,027✔
497
            throw NegativeNumberException::squareRootOfNegativeNumber();
3✔
498
        }
499

500
        $value = $this->value;
6,024✔
501
        $inputScale = $this->scale;
6,024✔
502

503
        if ($inputScale % 2 !== 0) {
6,024✔
504
            $value .= '0';
2,346✔
505
            $inputScale++;
2,346✔
506
        }
507

508
        $calculator = CalculatorRegistry::get();
6,024✔
509

510
        // Keep one extra digit for rounding.
511
        $intermediateScale = max($scale, intdiv($inputScale, 2)) + 1;
6,024✔
512
        $value .= str_repeat('0', 2 * $intermediateScale - $inputScale);
6,024✔
513

514
        $sqrt = $calculator->sqrt($value);
6,024✔
515
        $isExact = $calculator->mul($sqrt, $sqrt) === $value;
6,024✔
516

517
        if (! $isExact) {
6,024✔
518
            if ($roundingMode === RoundingMode::Unnecessary) {
3,774✔
519
                throw RoundingNecessaryException::decimalSquareRootNotExact();
375✔
520
            }
521

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

528
            // Irrational sqrt cannot land exactly on a midpoint; treat tie-to-down modes as HalfUp.
529
            elseif (in_array($roundingMode, [RoundingMode::HalfDown, RoundingMode::HalfEven, RoundingMode::HalfFloor], true)) {
2,625✔
530
                $roundingMode = RoundingMode::HalfUp;
1,125✔
531
            }
532
        }
533

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

536
        if ($scaled === null) {
5,649✔
537
            throw RoundingNecessaryException::decimalSquareRootScaleTooSmall();
42✔
538
        }
539

540
        return new BigDecimal($scaled, $scale);
5,607✔
541
    }
542

543
    /**
544
     * Returns a copy of this BigDecimal with the decimal point moved to the left by the given number of places.
545
     *
546
     * @pure
547
     */
548
    public function withPointMovedLeft(int $places): BigDecimal
549
    {
550
        if ($places === 0) {
231✔
551
            return $this;
33✔
552
        }
553

554
        if ($places < 0) {
198✔
555
            return $this->withPointMovedRight(-$places);
66✔
556
        }
557

558
        return new BigDecimal($this->value, $this->scale + $places);
132✔
559
    }
560

561
    /**
562
     * Returns a copy of this BigDecimal with the decimal point moved to the right by the given number of places.
563
     *
564
     * @pure
565
     */
566
    public function withPointMovedRight(int $places): BigDecimal
567
    {
568
        if ($places === 0) {
231✔
569
            return $this;
33✔
570
        }
571

572
        if ($places < 0) {
198✔
573
            return $this->withPointMovedLeft(-$places);
66✔
574
        }
575

576
        $value = $this->value;
132✔
577
        $scale = $this->scale - $places;
132✔
578

579
        if ($scale < 0) {
132✔
580
            if ($value !== '0') {
78✔
581
                $value .= str_repeat('0', -$scale);
60✔
582
            }
583
            $scale = 0;
78✔
584
        }
585

586
        return new BigDecimal($value, $scale);
132✔
587
    }
588

589
    /**
590
     * Returns a copy of this BigDecimal with any trailing zeros removed from the fractional part.
591
     *
592
     * @pure
593
     */
594
    public function strippedOfTrailingZeros(): BigDecimal
595
    {
596
        if ($this->scale === 0) {
453✔
597
            return $this;
114✔
598
        }
599

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

602
        if ($trimmedValue === '') {
339✔
603
            return BigDecimal::zero();
9✔
604
        }
605

606
        $trimmableZeros = strlen($this->value) - strlen($trimmedValue);
330✔
607

608
        if ($trimmableZeros === 0) {
330✔
609
            return $this;
282✔
610
        }
611

612
        if ($trimmableZeros > $this->scale) {
48✔
613
            $trimmableZeros = $this->scale;
12✔
614
        }
615

616
        $value = substr($this->value, 0, -$trimmableZeros);
48✔
617
        $scale = $this->scale - $trimmableZeros;
48✔
618

619
        return new BigDecimal($value, $scale);
48✔
620
    }
621

622
    #[Override]
623
    public function negated(): static
624
    {
625
        return new BigDecimal(CalculatorRegistry::get()->neg($this->value), $this->scale);
1,410✔
626
    }
627

628
    #[Override]
629
    public function compareTo(BigNumber|int|string $that): int
630
    {
631
        $that = BigNumber::of($that);
858✔
632

633
        if ($that instanceof BigInteger) {
858✔
634
            $that = $that->toBigDecimal();
504✔
635
        }
636

637
        if ($that instanceof BigDecimal) {
858✔
638
            [$a, $b] = $this->scaleValues($this, $that);
789✔
639

640
            return CalculatorRegistry::get()->cmp($a, $b);
789✔
641
        }
642

643
        return -$that->compareTo($this);
69✔
644
    }
645

646
    #[Override]
647
    public function getSign(): int
648
    {
649
        return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1);
9,294✔
650
    }
651

652
    /**
653
     * @pure
654
     */
655
    public function getUnscaledValue(): BigInteger
656
    {
657
        return self::newBigInteger($this->value);
2,892✔
658
    }
659

660
    /**
661
     * @pure
662
     */
663
    public function getScale(): int
664
    {
665
        return $this->scale;
2,892✔
666
    }
667

668
    /**
669
     * Returns the number of significant digits in the number.
670
     *
671
     * This is the number of digits in the unscaled value of the number.
672
     * The sign has no impact on the result.
673
     *
674
     * Examples:
675
     *   0 => 1
676
     *   0.0 => 1
677
     *   123 => 3
678
     *   123.456 => 6
679
     *   0.00123 => 3
680
     *   0.0012300 => 5
681
     *
682
     * @pure
683
     */
684
    public function getPrecision(): int
685
    {
686
        $length = strlen($this->value);
81✔
687

688
        return ($this->value[0] === '-') ? $length - 1 : $length;
81✔
689
    }
690

691
    /**
692
     * Returns whether this decimal number has a non-zero fractional part.
693
     *
694
     * @deprecated Will be removed in 0.17. Use `! $number->getFractionalPart()->isZero()` instead.
695
     */
696
    public function hasNonZeroFractionalPart(): bool
697
    {
698
        trigger_error(
18✔
699
            'BigDecimal::hasNonZeroFractionalPart() is deprecated and will be removed in 0.17. Use `! $number->getFractionalPart()->isZero()` instead.',
18✔
700
            E_USER_DEPRECATED,
18✔
701
        );
18✔
702

703
        return ! $this->getFractionalPart()->isZero();
18✔
704
    }
705

706
    /**
707
     * Returns the integral part of this decimal number.
708
     *
709
     * Examples:
710
     *
711
     * - `123.456` returns `123`
712
     * - `-123.456` returns `-123`
713
     * - `0.123` returns `0`
714
     * - `-0.123` returns `0`
715
     *
716
     * The following identity holds: `$d->isEqualTo($d->getFractionalPart()->plus($d->getIntegralPart()))`.
717
     *
718
     * @pure
719
     */
720
    public function getIntegralPart(): BigInteger
721
    {
722
        if ($this->scale === 0) {
54✔
723
            return self::newBigInteger($this->value);
9✔
724
        }
725

726
        $value = DecimalHelper::padUnscaledValue($this->value, $this->scale);
45✔
727
        $integerPart = substr($value, 0, -$this->scale);
45✔
728

729
        if ($integerPart === '-0') {
45✔
730
            $integerPart = '0';
3✔
731
        }
732

733
        return self::newBigInteger($integerPart);
45✔
734
    }
735

736
    /**
737
     * Returns the fractional part of this decimal number.
738
     *
739
     * Examples:
740
     *
741
     * - `123.456` returns `0.456`
742
     * - `-123.456` returns `-0.456`
743
     * - `123` returns `0`
744
     * - `-123` returns `0`
745
     *
746
     * The following identity holds: `$d->isEqualTo($d->getFractionalPart()->plus($d->getIntegralPart()))`.
747
     *
748
     * @pure
749
     */
750
    public function getFractionalPart(): BigDecimal
751
    {
752
        if ($this->scale === 0) {
60✔
753
            return BigDecimal::zero();
15✔
754
        }
755

756
        return $this->minus($this->getIntegralPart());
45✔
757
    }
758

759
    #[Override]
760
    public function toBigInteger(): BigInteger
761
    {
762
        if ($this->scale === 0) {
252✔
763
            return self::newBigInteger($this->value);
135✔
764
        }
765

766
        $rational = $this->toBigRational();
138✔
767
        $integralPart = $rational->getIntegralPart();
138✔
768

769
        if ($rational->isEqualTo($integralPart)) {
138✔
770
            return $integralPart;
87✔
771
        }
772

773
        throw RoundingNecessaryException::decimalNotConvertibleToInteger();
51✔
774
    }
775

776
    #[Override]
777
    public function toBigDecimal(): BigDecimal
778
    {
779
        return $this;
8,787✔
780
    }
781

782
    #[Override]
783
    public function toBigRational(): BigRational
784
    {
785
        $numerator = self::newBigInteger($this->value);
495✔
786
        $denominator = self::newBigInteger('1' . str_repeat('0', $this->scale));
495✔
787

788
        return self::newBigRational($numerator, $denominator, false, true);
495✔
789
    }
790

791
    #[Override]
792
    public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigDecimal
793
    {
794
        if ($scale < 0) {
75✔
795
            throw InvalidArgumentException::negativeScale();
×
796
        }
797

798
        if ($scale === $this->scale) {
75✔
799
            return $this;
15✔
800
        }
801

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

804
        if ($value === null) {
60✔
805
            throw RoundingNecessaryException::decimalScaleTooSmall();
9✔
806
        }
807

808
        return new BigDecimal($value, $scale);
51✔
809
    }
810

811
    #[Override]
812
    public function toInt(): int
813
    {
814
        return $this->toBigInteger()->toInt();
33✔
815
    }
816

817
    #[Override]
818
    public function toFloat(): float
819
    {
820
        return (float) $this->toString();
117✔
821
    }
822

823
    /**
824
     * @return numeric-string
825
     */
826
    #[Override]
827
    public function toString(): string
828
    {
829
        if ($this->scale === 0) {
7,203✔
830
            /** @var numeric-string */
831
            return $this->value;
1,593✔
832
        }
833

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

836
        /** @phpstan-ignore return.type */
837
        return substr($value, 0, -$this->scale) . '.' . substr($value, -$this->scale);
5,655✔
838
    }
839

840
    /**
841
     * This method is required for serializing the object and SHOULD NOT be accessed directly.
842
     *
843
     * @internal
844
     *
845
     * @return array{value: string, scale: int}
846
     */
847
    public function __serialize(): array
848
    {
849
        return ['value' => $this->value, 'scale' => $this->scale];
3✔
850
    }
851

852
    /**
853
     * This method is only here to allow unserializing the object and cannot be accessed directly.
854
     *
855
     * @internal
856
     *
857
     * @param array{value: string, scale: int} $data
858
     *
859
     * @throws LogicException
860
     */
861
    public function __unserialize(array $data): void
862
    {
863
        /** @phpstan-ignore isset.initializedProperty */
864
        if (isset($this->value)) {
6✔
865
            throw new LogicException('__unserialize() is an internal function, it must not be called directly.');
3✔
866
        }
867

868
        /** @phpstan-ignore deadCode.unreachable */
869
        $this->value = $data['value'];
3✔
870
        $this->scale = $data['scale'];
3✔
871
    }
872

873
    #[Override]
874
    protected static function from(BigNumber $number): static
875
    {
876
        return $number->toBigDecimal();
11,682✔
877
    }
878

879
    /**
880
     * Puts the internal values of the given decimal numbers on the same scale.
881
     *
882
     * @return array{string, string} The scaled integer values of $x and $y.
883
     *
884
     * @pure
885
     */
886
    private function scaleValues(BigDecimal $x, BigDecimal $y): array
887
    {
888
        $a = $x->value;
1,326✔
889
        $b = $y->value;
1,326✔
890

891
        if ($b !== '0' && $x->scale > $y->scale) {
1,326✔
892
            $b .= str_repeat('0', $x->scale - $y->scale);
513✔
893
        } elseif ($a !== '0' && $x->scale < $y->scale) {
870✔
894
            $a .= str_repeat('0', $y->scale - $x->scale);
198✔
895
        }
896

897
        return [$a, $b];
1,326✔
898
    }
899

900
    /**
901
     * @pure
902
     */
903
    private function valueWithMinScale(int $scale): string
904
    {
905
        $value = $this->value;
1,932✔
906

907
        if ($this->value !== '0' && $scale > $this->scale) {
1,932✔
908
            $value .= str_repeat('0', $scale - $this->scale);
1,770✔
909
        }
910

911
        return $value;
1,932✔
912
    }
913

914
    /**
915
     * @pure
916
     */
917
    private function isOneScaleZero(): bool
918
    {
919
        return $this->scale === 0 && $this->value === '1';
2,136✔
920
    }
921
}
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