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

brick / math / 21788757427

07 Feb 2026 09:34PM UTC coverage: 98.528% (-0.8%) from 99.372%
21788757427

push

github

BenMorel
Remove deprecated BigRational::simplified()

1339 of 1359 relevant lines covered (98.53%)

2291.11 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\Constants;
14
use Brick\Math\Internal\DecimalHelper;
15
use LogicException;
16
use Override;
17

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

27
use const E_USER_DEPRECATED;
28

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

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

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

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

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

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

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

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

113
        return $zero;
30✔
114
    }
115

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

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

130
        return $one;
30✔
131
    }
132

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

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

147
        return $ten;
3✔
148
    }
149

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

343
        if ($exponent < 0 || $exponent > Constants::MAX_POWER) {
72✔
344
            throw InvalidArgumentException::exponentOutOfRange($exponent, 0, Constants::MAX_POWER);
6✔
345
        }
346

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

624
        if ($that instanceof BigInteger) {
825✔
625
            $that = $that->toBigDecimal();
486✔
626
        }
627

628
        if ($that instanceof BigDecimal) {
825✔
629
            [$a, $b] = $this->scaleValues($this, $that);
771✔
630

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

634
        return -$that->compareTo($this);
54✔
635
    }
636

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

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

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

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

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

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

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

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

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

720
        // Handle edge case where integral part is "-0"
721
        if ($integerPart === '-0') {
45✔
722
            $integerPart = '0';
3✔
723
        }
724

725
        return self::newBigInteger($integerPart);
45✔
726
    }
727

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

748
        return $this->minus($this->getIntegralPart());
45✔
749
    }
750

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

758
        $rational = $this->toBigRational();
138✔
759
        $integralPart = $rational->getIntegralPart();
138✔
760

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

765
        throw RoundingNecessaryException::decimalNotConvertibleToInteger();
51✔
766
    }
767

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

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

780
        return self::newBigRational($numerator, $denominator, false, true);
471✔
781
    }
782

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

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

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

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

800
        return new BigDecimal($value, $scale);
51✔
801
    }
802

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

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

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

826
        $value = DecimalHelper::padUnscaledValue($this->value, $this->scale);
5,646✔
827

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

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

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

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

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

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

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

889
        return [$a, $b];
1,317✔
890
    }
891

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

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

903
        return $value;
1,932✔
904
    }
905
}
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