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

brick / math / 21756136919

06 Feb 2026 03:33PM UTC coverage: 99.175% (+0.003%) from 99.172%
21756136919

push

github

BenMorel
Uniformize exception messages

10 of 10 new or added lines in 2 files covered. (100.0%)

1 existing line in 1 file now uncovered.

1323 of 1334 relevant lines covered (99.18%)

2326.19 hits per line

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

99.55
/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\Calculator;
13
use Brick\Math\Internal\CalculatorRegistry;
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

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

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

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

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

82
        if ($scale < 0) {
279✔
83
            if ($value !== '0') {
66✔
84
                $value .= str_repeat('0', -$scale);
54✔
85
            }
86
            $scale = 0;
66✔
87
        }
88

89
        return new BigDecimal($value, $scale);
279✔
90
    }
91

92
    /**
93
     * Returns a BigDecimal representing zero, with a scale of zero.
94
     *
95
     * @pure
96
     */
97
    public static function zero(): BigDecimal
98
    {
99
        /** @var BigDecimal|null $zero */
100
        static $zero;
15✔
101

102
        if ($zero === null) {
15✔
103
            $zero = new BigDecimal('0');
3✔
104
        }
105

106
        return $zero;
15✔
107
    }
108

109
    /**
110
     * Returns a BigDecimal representing one, with a scale of zero.
111
     *
112
     * @pure
113
     */
114
    public static function one(): BigDecimal
115
    {
116
        /** @var BigDecimal|null $one */
117
        static $one;
30✔
118

119
        if ($one === null) {
30✔
120
            $one = new BigDecimal('1');
3✔
121
        }
122

123
        return $one;
30✔
124
    }
125

126
    /**
127
     * Returns a BigDecimal representing ten, with a scale of zero.
128
     *
129
     * @pure
130
     */
131
    public static function ten(): BigDecimal
132
    {
133
        /** @var BigDecimal|null $ten */
134
        static $ten;
3✔
135

136
        if ($ten === null) {
3✔
137
            $ten = new BigDecimal('10');
3✔
138
        }
139

140
        return $ten;
3✔
141
    }
142

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

158
        if ($that->value === '0' && $that->scale <= $this->scale) {
222✔
159
            return $this;
12✔
160
        }
161

162
        if ($this->value === '0' && $this->scale <= $that->scale) {
210✔
163
            return $that;
42✔
164
        }
165

166
        [$a, $b] = $this->scaleValues($this, $that);
192✔
167

168
        $value = CalculatorRegistry::get()->add($a, $b);
192✔
169
        $scale = $this->scale > $that->scale ? $this->scale : $that->scale;
192✔
170

171
        return new BigDecimal($value, $scale);
192✔
172
    }
173

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

189
        if ($that->value === '0' && $that->scale <= $this->scale) {
135✔
190
            return $this;
6✔
191
        }
192

193
        [$a, $b] = $this->scaleValues($this, $that);
129✔
194

195
        $value = CalculatorRegistry::get()->sub($a, $b);
129✔
196
        $scale = $this->scale > $that->scale ? $this->scale : $that->scale;
129✔
197

198
        return new BigDecimal($value, $scale);
129✔
199
    }
200

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

216
        if ($that->value === '1' && $that->scale === 0) {
246✔
217
            return $this;
15✔
218
        }
219

220
        if ($this->value === '1' && $this->scale === 0) {
231✔
221
            return $that;
9✔
222
        }
223

224
        $value = CalculatorRegistry::get()->mul($this->value, $that->value);
222✔
225
        $scale = $this->scale + $that->scale;
222✔
226

227
        return new BigDecimal($value, $scale);
222✔
228
    }
229

230
    /**
231
     * Returns the result of the division of this number by the given one, at the given scale.
232
     *
233
     * @param BigNumber|int|string $that         The divisor. Must be convertible to a BigDecimal.
234
     * @param int                  $scale        The desired scale.
235
     * @param RoundingMode         $roundingMode An optional rounding mode, defaults to Unnecessary.
236
     *
237
     * @throws InvalidArgumentException   If the scale is invalid.
238
     * @throws MathException              If the divisor is not a valid number or is not convertible to a BigDecimal.
239
     * @throws DivisionByZeroException    If the divisor is zero.
240
     * @throws RoundingNecessaryException If RoundingMode::Unnecessary is used and the result cannot be represented exactly at the given scale.
241
     *
242
     * @pure
243
     */
244
    public function dividedBy(BigNumber|int|string $that, int $scale, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigDecimal
245
    {
246
        $that = BigDecimal::of($that);
1,905✔
247

248
        if ($that->isZero()) {
1,905✔
249
            throw DivisionByZeroException::divisionByZero();
12✔
250
        }
251

252
        if ($scale < 0) {
1,893✔
253
            throw InvalidArgumentException::negativeScale();
3✔
254
        }
255

256
        if ($that->value === '1' && $that->scale === 0 && $scale === $this->scale) {
1,890✔
257
            return $this;
114✔
258
        }
259

260
        $p = $this->valueWithMinScale($that->scale + $scale);
1,776✔
261
        $q = $that->valueWithMinScale($this->scale - $scale);
1,776✔
262

263
        $calculator = CalculatorRegistry::get();
1,776✔
264
        $result = $calculator->divRound($p, $q, $roundingMode);
1,776✔
265

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

269
            $denominator = $calculator->divQ($b, $calculator->gcd($a, $b));
129✔
270
            $requiredScale = DecimalHelper::computeScaleFromReducedFractionDenominator($denominator);
129✔
271

272
            if ($requiredScale === null) {
129✔
273
                throw RoundingNecessaryException::decimalDivisionNotExact();
9✔
274
            }
275

276
            throw RoundingNecessaryException::decimalDivisionScaleTooSmall();
120✔
277
        }
278

279
        return new BigDecimal($result, $scale);
1,647✔
280
    }
281

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

299
        if ($that->value === '0') {
93✔
300
            throw DivisionByZeroException::divisionByZero();
9✔
301
        }
302

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

305
        $calculator = CalculatorRegistry::get();
84✔
306

307
        $denominator = $calculator->divQ($b, $calculator->gcd($a, $b));
84✔
308
        $scale = DecimalHelper::computeScaleFromReducedFractionDenominator($denominator);
84✔
309

310
        if ($scale === null) {
84✔
311
            throw RoundingNecessaryException::decimalDivisionNotExact();
12✔
312
        }
313

314
        return $this->dividedBy($that, $scale)->strippedOfTrailingZeros();
72✔
315
    }
316

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

332
        if ($exponent === 1) {
93✔
333
            return $this;
21✔
334
        }
335

336
        if ($exponent < 0 || $exponent > Calculator::MAX_POWER) {
72✔
337
            throw InvalidArgumentException::exponentOutOfRange($exponent, 0, Calculator::MAX_POWER);
6✔
338
        }
339

340
        return new BigDecimal(CalculatorRegistry::get()->pow($this->value, $exponent), $this->scale * $exponent);
66✔
341
    }
342

343
    /**
344
     * Returns the quotient of the division of this number by the given one.
345
     *
346
     * The quotient has a scale of `0`.
347
     *
348
     * @param BigNumber|int|string $that The divisor. Must be convertible to a BigDecimal.
349
     *
350
     * @throws MathException           If the divisor is not a valid decimal number.
351
     * @throws DivisionByZeroException If the divisor is zero.
352
     *
353
     * @pure
354
     */
355
    public function quotient(BigNumber|int|string $that): BigDecimal
356
    {
357
        $that = BigDecimal::of($that);
159✔
358

359
        if ($that->isZero()) {
159✔
360
            throw DivisionByZeroException::divisionByZero();
3✔
361
        }
362

363
        $p = $this->valueWithMinScale($that->scale);
156✔
364
        $q = $that->valueWithMinScale($this->scale);
156✔
365

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

368
        return new BigDecimal($quotient, 0);
156✔
369
    }
370

371
    /**
372
     * Returns the remainder of the division of this number by the given one.
373
     *
374
     * The remainder has a scale of `max($this->scale, $that->scale)`.
375
     *
376
     * @param BigNumber|int|string $that The divisor. Must be convertible to a BigDecimal.
377
     *
378
     * @throws MathException           If the divisor is not a valid decimal number.
379
     * @throws DivisionByZeroException If the divisor is zero.
380
     *
381
     * @pure
382
     */
383
    public function remainder(BigNumber|int|string $that): BigDecimal
384
    {
385
        $that = BigDecimal::of($that);
159✔
386

387
        if ($that->isZero()) {
159✔
388
            throw DivisionByZeroException::divisionByZero();
3✔
389
        }
390

391
        $p = $this->valueWithMinScale($that->scale);
156✔
392
        $q = $that->valueWithMinScale($this->scale);
156✔
393

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

396
        $scale = $this->scale > $that->scale ? $this->scale : $that->scale;
156✔
397

398
        return new BigDecimal($remainder, $scale);
156✔
399
    }
400

401
    /**
402
     * Returns the quotient and remainder of the division of this number by the given one.
403
     *
404
     * The quotient has a scale of `0`, and the remainder has a scale of `max($this->scale, $that->scale)`.
405
     *
406
     * @param BigNumber|int|string $that The divisor. Must be convertible to a BigDecimal.
407
     *
408
     * @return array{BigDecimal, BigDecimal} An array containing the quotient and the remainder.
409
     *
410
     * @throws MathException           If the divisor is not a valid decimal number.
411
     * @throws DivisionByZeroException If the divisor is zero.
412
     *
413
     * @pure
414
     */
415
    public function quotientAndRemainder(BigNumber|int|string $that): array
416
    {
417
        $that = BigDecimal::of($that);
159✔
418

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

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

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

428
        $scale = $this->scale > $that->scale ? $this->scale : $that->scale;
156✔
429

430
        $quotient = new BigDecimal($quotient, 0);
156✔
431
        $remainder = new BigDecimal($remainder, $scale);
156✔
432

433
        return [$quotient, $remainder];
156✔
434
    }
435

436
    /**
437
     * Returns the square root of this number, rounded to the given scale according to the given rounding mode.
438
     *
439
     * @param int          $scale        The target scale.
440
     * @param RoundingMode $roundingMode The rounding mode to use, defaults to Unnecessary.
441
     *
442
     * @throws InvalidArgumentException   If the scale is negative.
443
     * @throws NegativeNumberException    If this number is negative.
444
     * @throws RoundingNecessaryException If RoundingMode::Unnecessary is used, but rounding is necessary.
445
     *
446
     * @pure
447
     */
448
    public function sqrt(int $scale, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigDecimal
449
    {
450
        if ($scale < 0) {
6,150✔
451
            throw InvalidArgumentException::negativeScale();
3✔
452
        }
453

454
        if ($this->value === '0') {
6,147✔
455
            return new BigDecimal('0', $scale);
120✔
456
        }
457

458
        if ($this->value[0] === '-') {
6,027✔
459
            throw NegativeNumberException::squareRootOfNegativeNumber();
3✔
460
        }
461

462
        $value = $this->value;
6,024✔
463
        $inputScale = $this->scale;
6,024✔
464

465
        if ($inputScale % 2 !== 0) {
6,024✔
466
            $value .= '0';
2,346✔
467
            $inputScale++;
2,346✔
468
        }
469

470
        $calculator = CalculatorRegistry::get();
6,024✔
471

472
        // Keep one extra digit for rounding.
473
        $intermediateScale = max($scale, intdiv($inputScale, 2)) + 1;
6,024✔
474
        $value .= str_repeat('0', 2 * $intermediateScale - $inputScale);
6,024✔
475

476
        $sqrt = $calculator->sqrt($value);
6,024✔
477
        $isExact = $calculator->mul($sqrt, $sqrt) === $value;
6,024✔
478

479
        if (! $isExact) {
6,024✔
480
            if ($roundingMode === RoundingMode::Unnecessary) {
3,774✔
481
                throw RoundingNecessaryException::decimalSquareRootNotExact();
375✔
482
            }
483

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

490
            // Irrational sqrt cannot land exactly on a midpoint; treat tie-to-down modes as HalfUp.
491
            elseif (in_array($roundingMode, [RoundingMode::HalfDown, RoundingMode::HalfEven, RoundingMode::HalfFloor], true)) {
2,625✔
492
                $roundingMode = RoundingMode::HalfUp;
1,125✔
493
            }
494
        }
495

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

498
        if ($scaled === null) {
5,649✔
499
            throw RoundingNecessaryException::decimalSquareRootScaleTooSmall();
42✔
500
        }
501

502
        return new BigDecimal($scaled, $scale);
5,607✔
503
    }
504

505
    /**
506
     * Returns a copy of this BigDecimal with the decimal point moved $n places to the left.
507
     *
508
     * @pure
509
     */
510
    public function withPointMovedLeft(int $places): BigDecimal
511
    {
512
        if ($places === 0) {
231✔
513
            return $this;
33✔
514
        }
515

516
        if ($places < 0) {
198✔
517
            return $this->withPointMovedRight(-$places);
66✔
518
        }
519

520
        return new BigDecimal($this->value, $this->scale + $places);
132✔
521
    }
522

523
    /**
524
     * Returns a copy of this BigDecimal with the decimal point moved $n places to the right.
525
     *
526
     * @pure
527
     */
528
    public function withPointMovedRight(int $places): BigDecimal
529
    {
530
        if ($places === 0) {
231✔
531
            return $this;
33✔
532
        }
533

534
        if ($places < 0) {
198✔
535
            return $this->withPointMovedLeft(-$places);
66✔
536
        }
537

538
        $value = $this->value;
132✔
539
        $scale = $this->scale - $places;
132✔
540

541
        if ($scale < 0) {
132✔
542
            if ($value !== '0') {
78✔
543
                $value .= str_repeat('0', -$scale);
60✔
544
            }
545
            $scale = 0;
78✔
546
        }
547

548
        return new BigDecimal($value, $scale);
132✔
549
    }
550

551
    /**
552
     * Returns a copy of this BigDecimal with any trailing zeros removed from the fractional part.
553
     *
554
     * @pure
555
     */
556
    public function strippedOfTrailingZeros(): BigDecimal
557
    {
558
        if ($this->scale === 0) {
453✔
559
            return $this;
114✔
560
        }
561

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

564
        if ($trimmedValue === '') {
339✔
565
            return BigDecimal::zero();
9✔
566
        }
567

568
        $trimmableZeros = strlen($this->value) - strlen($trimmedValue);
330✔
569

570
        if ($trimmableZeros === 0) {
330✔
571
            return $this;
282✔
572
        }
573

574
        if ($trimmableZeros > $this->scale) {
48✔
575
            $trimmableZeros = $this->scale;
12✔
576
        }
577

578
        $value = substr($this->value, 0, -$trimmableZeros);
48✔
579
        $scale = $this->scale - $trimmableZeros;
48✔
580

581
        return new BigDecimal($value, $scale);
48✔
582
    }
583

584
    #[Override]
585
    public function negated(): static
586
    {
587
        return new BigDecimal(CalculatorRegistry::get()->neg($this->value), $this->scale);
1,401✔
588
    }
589

590
    #[Override]
591
    public function compareTo(BigNumber|int|string $that): int
592
    {
593
        $that = BigNumber::of($that);
783✔
594

595
        if ($that instanceof BigInteger) {
783✔
596
            $that = $that->toBigDecimal();
486✔
597
        }
598

599
        if ($that instanceof BigDecimal) {
783✔
600
            [$a, $b] = $this->scaleValues($this, $that);
729✔
601

602
            return CalculatorRegistry::get()->cmp($a, $b);
729✔
603
        }
604

605
        return -$that->compareTo($this);
54✔
606
    }
607

608
    #[Override]
609
    public function getSign(): int
610
    {
611
        return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1);
2,508✔
612
    }
613

614
    /**
615
     * @pure
616
     */
617
    public function getUnscaledValue(): BigInteger
618
    {
619
        return self::newBigInteger($this->value);
2,892✔
620
    }
621

622
    /**
623
     * @pure
624
     */
625
    public function getScale(): int
626
    {
627
        return $this->scale;
2,892✔
628
    }
629

630
    /**
631
     * Returns the number of significant digits in the number.
632
     *
633
     * This is the number of digits in the unscaled value of the number.
634
     * The sign has no impact on the result.
635
     *
636
     * Examples:
637
     *   0 => 1
638
     *   0.0 => 1
639
     *   123 => 3
640
     *   123.456 => 6
641
     *   0.00123 => 3
642
     *   0.0012300 => 5
643
     *
644
     * @pure
645
     */
646
    public function getPrecision(): int
647
    {
648
        $length = strlen($this->value);
81✔
649

650
        return ($this->value[0] === '-') ? $length - 1 : $length;
81✔
651
    }
652

653
    /**
654
     * Returns whether this decimal number has a non-zero fractional part.
655
     *
656
     * @pure
657
     */
658
    public function hasNonZeroFractionalPart(): bool
659
    {
660
        if ($this->scale === 0) {
18✔
661
            return false;
6✔
662
        }
663

664
        $value = DecimalHelper::padUnscaledValue($this->value, $this->scale);
12✔
665

666
        return substr($value, -$this->scale) !== str_repeat('0', $this->scale);
12✔
667
    }
668

669
    #[Override]
670
    public function toBigInteger(): BigInteger
671
    {
672
        if ($this->scale === 0) {
252✔
673
            return self::newBigInteger($this->value);
135✔
674
        }
675

676
        $rational = $this->toBigRational();
138✔
677
        $integralPart = $rational->getIntegralPart();
138✔
678

679
        if ($rational->isEqualTo($integralPart)) {
138✔
680
            return $integralPart;
87✔
681
        }
682

683
        throw RoundingNecessaryException::decimalNotConvertibleToInteger();
51✔
684
    }
685

686
    #[Override]
687
    public function toBigDecimal(): BigDecimal
688
    {
689
        return $this;
8,754✔
690
    }
691

692
    #[Override]
693
    public function toBigRational(): BigRational
694
    {
695
        $numerator = self::newBigInteger($this->value);
474✔
696
        $denominator = self::newBigInteger('1' . str_repeat('0', $this->scale));
474✔
697

698
        return self::newBigRational($numerator, $denominator, false, true);
474✔
699
    }
700

701
    #[Override]
702
    public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigDecimal
703
    {
704
        if ($scale === $this->scale) {
75✔
705
            return $this;
15✔
706
        }
707

708
        if ($scale < 0) {
60✔
UNCOV
709
            throw InvalidArgumentException::negativeScale();
×
710
        }
711

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

714
        if ($value === null) {
60✔
715
            throw RoundingNecessaryException::decimalScaleTooSmall();
9✔
716
        }
717

718
        return new BigDecimal($value, $scale);
51✔
719
    }
720

721
    #[Override]
722
    public function toInt(): int
723
    {
724
        return $this->toBigInteger()->toInt();
33✔
725
    }
726

727
    #[Override]
728
    public function toFloat(): float
729
    {
730
        return (float) $this->toString();
117✔
731
    }
732

733
    /**
734
     * @return numeric-string
735
     */
736
    #[Override]
737
    public function toString(): string
738
    {
739
        if ($this->scale === 0) {
7,152✔
740
            /** @var numeric-string */
741
            return $this->value;
1,584✔
742
        }
743

744
        $value = DecimalHelper::padUnscaledValue($this->value, $this->scale);
5,613✔
745

746
        /** @phpstan-ignore return.type */
747
        return substr($value, 0, -$this->scale) . '.' . substr($value, -$this->scale);
5,613✔
748
    }
749

750
    /**
751
     * This method is required for serializing the object and SHOULD NOT be accessed directly.
752
     *
753
     * @internal
754
     *
755
     * @return array{value: string, scale: int}
756
     */
757
    public function __serialize(): array
758
    {
759
        return ['value' => $this->value, 'scale' => $this->scale];
3✔
760
    }
761

762
    /**
763
     * This method is only here to allow unserializing the object and cannot be accessed directly.
764
     *
765
     * @internal
766
     *
767
     * @param array{value: string, scale: int} $data
768
     *
769
     * @throws LogicException
770
     */
771
    public function __unserialize(array $data): void
772
    {
773
        /** @phpstan-ignore isset.initializedProperty */
774
        if (isset($this->value)) {
6✔
775
            throw new LogicException('__unserialize() is an internal function, it must not be called directly.');
3✔
776
        }
777

778
        /** @phpstan-ignore deadCode.unreachable */
779
        $this->value = $data['value'];
3✔
780
        $this->scale = $data['scale'];
3✔
781
    }
782

783
    #[Override]
784
    protected static function from(BigNumber $number): static
785
    {
786
        return $number->toBigDecimal();
11,643✔
787
    }
788

789
    /**
790
     * Puts the internal values of the given decimal numbers on the same scale.
791
     *
792
     * @return array{string, string} The scaled integer values of $x and $y.
793
     *
794
     * @pure
795
     */
796
    private function scaleValues(BigDecimal $x, BigDecimal $y): array
797
    {
798
        $a = $x->value;
1,263✔
799
        $b = $y->value;
1,263✔
800

801
        if ($b !== '0' && $x->scale > $y->scale) {
1,263✔
802
            $b .= str_repeat('0', $x->scale - $y->scale);
459✔
803
        } elseif ($a !== '0' && $x->scale < $y->scale) {
837✔
804
            $a .= str_repeat('0', $y->scale - $x->scale);
198✔
805
        }
806

807
        return [$a, $b];
1,263✔
808
    }
809

810
    /**
811
     * @pure
812
     */
813
    private function valueWithMinScale(int $scale): string
814
    {
815
        $value = $this->value;
1,932✔
816

817
        if ($this->value !== '0' && $scale > $this->scale) {
1,932✔
818
            $value .= str_repeat('0', $scale - $this->scale);
1,770✔
819
        }
820

821
        return $value;
1,932✔
822
    }
823
}
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