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

brick / math / 21912480341

11 Feb 2026 03:55PM UTC coverage: 98.91% (-0.002%) from 98.912%
21912480341

push

github

BenMorel
Rename isBitSet() and randomBits() parameters

6 of 6 new or added lines in 1 file covered. (100.0%)

99 existing lines in 3 files now uncovered.

1361 of 1376 relevant lines covered (98.91%)

2317.93 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\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

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

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

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

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

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

92
        return new BigDecimal($value, $scale);
279✔
93
    }
94

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

105
        if ($zero === null) {
15✔
106
            $zero = new BigDecimal('0');
3✔
107
        }
108

109
        return $zero;
15✔
110
    }
111

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

122
        if ($one === null) {
30✔
123
            $one = new BigDecimal('1');
3✔
124
        }
125

126
        return $one;
30✔
127
    }
128

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

139
        if ($ten === null) {
3✔
140
            $ten = new BigDecimal('10');
3✔
141
        }
142

143
        return $ten;
3✔
144
    }
145

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

161
        if ($that->isZero() && $that->scale <= $this->scale) {
222✔
162
            return $this;
12✔
163
        }
164

165
        if ($this->isZero() && $this->scale <= $that->scale) {
210✔
166
            return $that;
42✔
167
        }
168

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

171
        $value = CalculatorRegistry::get()->add($a, $b);
192✔
172
        $scale = max($this->scale, $that->scale);
192✔
173

174
        return new BigDecimal($value, $scale);
192✔
175
    }
176

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

192
        if ($that->isZero() && $that->scale <= $this->scale) {
135✔
193
            return $this;
6✔
194
        }
195

196
        if ($this->isZero() && $this->scale <= $that->scale) {
129✔
197
            return $that->negated();
9✔
198
        }
199

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

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

205
        return new BigDecimal($value, $scale);
120✔
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 valid, 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->isOneScaleZero()) {
246✔
224
            return $this;
15✔
225
        }
226

227
        if ($this->isOneScaleZero()) {
231✔
228
            return $that;
9✔
229
        }
230

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

235
        $value = CalculatorRegistry::get()->mul($this->value, $that->value);
168✔
236
        $scale = $this->scale + $that->scale;
168✔
237

238
        return new BigDecimal($value, $scale);
168✔
239
    }
240

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

262
        $that = BigDecimal::of($that);
1,914✔
263

264
        if ($that->isZero()) {
1,914✔
265
            throw DivisionByZeroException::divisionByZero();
12✔
266
        }
267

268
        if ($that->isOneScaleZero() && $scale === $this->scale) {
1,902✔
269
            return $this;
114✔
270
        }
271

272
        $p = $this->valueWithMinScale($that->scale + $scale);
1,788✔
273
        $q = $that->valueWithMinScale($this->scale - $scale);
1,788✔
274

275
        $calculator = CalculatorRegistry::get();
1,788✔
276
        $result = $calculator->divRound($p, $q, $roundingMode);
1,788✔
277

278
        if ($result === null) {
1,788✔
279
            [$a, $b] = $this->scaleValues($this->abs(), $that->abs());
135✔
280

281
            $denominator = $calculator->divQ($b, $calculator->gcd($a, $b));
135✔
282
            $requiredScale = DecimalHelper::computeScaleFromReducedFractionDenominator($denominator);
135✔
283

284
            if ($requiredScale === null) {
135✔
285
                throw RoundingNecessaryException::decimalDivisionNotExact();
9✔
286
            }
287

288
            throw RoundingNecessaryException::decimalDivisionScaleTooSmall();
126✔
289
        }
290

291
        return new BigDecimal($result, $scale);
1,653✔
292
    }
293

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

311
        if ($that->isZero()) {
93✔
312
            throw DivisionByZeroException::divisionByZero();
9✔
313
        }
314

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

317
        $calculator = CalculatorRegistry::get();
84✔
318

319
        $denominator = $calculator->divQ($b, $calculator->gcd($a, $b));
84✔
320
        $scale = DecimalHelper::computeScaleFromReducedFractionDenominator($denominator);
84✔
321

322
        if ($scale === null) {
84✔
323
            throw RoundingNecessaryException::decimalDivisionNotExact();
12✔
324
        }
325

326
        return $this->dividedBy($that, $scale)->strippedOfTrailingZeros();
72✔
327
    }
328

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

344
        if ($exponent === 1) {
90✔
345
            return $this;
21✔
346
        }
347

348
        if ($exponent < 0) {
69✔
349
            throw InvalidArgumentException::negativeExponent();
3✔
350
        }
351

352
        return new BigDecimal(CalculatorRegistry::get()->pow($this->value, $exponent), $this->scale * $exponent);
66✔
353
    }
354

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

378
        if ($that->isZero()) {
159✔
379
            throw DivisionByZeroException::divisionByZero();
3✔
380
        }
381

382
        $p = $this->valueWithMinScale($that->scale);
156✔
383
        $q = $that->valueWithMinScale($this->scale);
156✔
384

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

387
        return new BigDecimal($quotient, 0);
156✔
388
    }
389

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

414
        if ($that->isZero()) {
159✔
415
            throw DivisionByZeroException::divisionByZero();
3✔
416
        }
417

418
        $p = $this->valueWithMinScale($that->scale);
156✔
419
        $q = $that->valueWithMinScale($this->scale);
156✔
420

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

423
        $scale = max($this->scale, $that->scale);
156✔
424

425
        return new BigDecimal($remainder, $scale);
156✔
426
    }
427

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

453
        if ($that->isZero()) {
159✔
454
            throw DivisionByZeroException::divisionByZero();
3✔
455
        }
456

457
        $p = $this->valueWithMinScale($that->scale);
156✔
458
        $q = $that->valueWithMinScale($this->scale);
156✔
459

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

462
        $scale = max($this->scale, $that->scale);
156✔
463

464
        $quotient = new BigDecimal($quotient, 0);
156✔
465
        $remainder = new BigDecimal($remainder, $scale);
156✔
466

467
        return [$quotient, $remainder];
156✔
468
    }
469

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

489
        if ($this->isZero()) {
6,147✔
490
            return new BigDecimal('0', $scale);
120✔
491
        }
492

493
        if ($this->isNegative()) {
6,027✔
494
            throw NegativeNumberException::squareRootOfNegativeNumber();
3✔
495
        }
496

497
        $value = $this->value;
6,024✔
498
        $inputScale = $this->scale;
6,024✔
499

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

505
        $calculator = CalculatorRegistry::get();
6,024✔
506

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

511
        $sqrt = $calculator->sqrt($value);
6,024✔
512
        $isExact = $calculator->mul($sqrt, $sqrt) === $value;
6,024✔
513

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

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

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

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

533
        if ($scaled === null) {
5,649✔
534
            throw RoundingNecessaryException::decimalSquareRootScaleTooSmall();
42✔
535
        }
536

537
        return new BigDecimal($scaled, $scale);
5,607✔
538
    }
539

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

551
        if ($places < 0) {
198✔
552
            return $this->withPointMovedRight(-$places);
66✔
553
        }
554

555
        return new BigDecimal($this->value, $this->scale + $places);
132✔
556
    }
557

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

569
        if ($places < 0) {
198✔
570
            return $this->withPointMovedLeft(-$places);
66✔
571
        }
572

573
        $value = $this->value;
132✔
574
        $scale = $this->scale - $places;
132✔
575

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

583
        return new BigDecimal($value, $scale);
132✔
584
    }
585

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

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

599
        if ($trimmedValue === '') {
339✔
600
            return BigDecimal::zero();
9✔
601
        }
602

603
        $trimmableZeros = strlen($this->value) - strlen($trimmedValue);
330✔
604

605
        if ($trimmableZeros === 0) {
330✔
606
            return $this;
282✔
607
        }
608

609
        if ($trimmableZeros > $this->scale) {
48✔
610
            $trimmableZeros = $this->scale;
12✔
611
        }
612

613
        $value = substr($this->value, 0, -$trimmableZeros);
48✔
614
        $scale = $this->scale - $trimmableZeros;
48✔
615

616
        return new BigDecimal($value, $scale);
48✔
617
    }
618

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

625
    #[Override]
626
    public function compareTo(BigNumber|int|string $that): int
627
    {
628
        $that = BigNumber::of($that);
816✔
629

630
        if ($that instanceof BigInteger) {
816✔
631
            $that = $that->toBigDecimal();
504✔
632
        }
633

634
        if ($that instanceof BigDecimal) {
816✔
635
            [$a, $b] = $this->scaleValues($this, $that);
747✔
636

637
            return CalculatorRegistry::get()->cmp($a, $b);
747✔
638
        }
639

640
        return -$that->compareTo($this);
69✔
641
    }
642

643
    #[Override]
644
    public function getSign(): int
645
    {
646
        return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1);
9,246✔
647
    }
648

649
    /**
650
     * @pure
651
     */
652
    public function getUnscaledValue(): BigInteger
653
    {
654
        return self::newBigInteger($this->value);
2,898✔
655
    }
656

657
    /**
658
     * @pure
659
     */
660
    public function getScale(): int
661
    {
662
        return $this->scale;
2,898✔
663
    }
664

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

685
        return ($this->value[0] === '-') ? $length - 1 : $length;
81✔
686
    }
687

688
    /**
689
     * Returns whether this decimal number has a non-zero fractional part.
690
     *
691
     * @pure
692
     */
693
    public function hasNonZeroFractionalPart(): bool
694
    {
695
        if ($this->scale === 0) {
18✔
696
            return false;
6✔
697
        }
698

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

701
        return substr($value, -$this->scale) !== str_repeat('0', $this->scale);
12✔
702
    }
703

704
    #[Override]
705
    public function toBigInteger(): BigInteger
706
    {
707
        $value = DecimalHelper::tryScaleExactly($this->value, $this->scale, 0);
252✔
708

709
        if ($value !== null) {
252✔
710
            return self::newBigInteger($value);
201✔
711
        }
712

713
        throw RoundingNecessaryException::decimalNotConvertibleToInteger();
51✔
714
    }
715

716
    #[Override]
717
    public function toBigDecimal(): BigDecimal
718
    {
719
        return $this;
8,778✔
720
    }
721

722
    #[Override]
723
    public function toBigRational(): BigRational
724
    {
725
        $numerator = self::newBigInteger($this->value);
372✔
726
        $denominator = self::newBigInteger('1' . str_repeat('0', $this->scale));
372✔
727

728
        return self::newBigRational($numerator, $denominator, false, true);
372✔
729
    }
730

731
    #[Override]
732
    public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigDecimal
733
    {
734
        if ($scale < 0) {
75✔
UNCOV
735
            throw InvalidArgumentException::negativeScale();
×
736
        }
737

738
        if ($scale === $this->scale) {
75✔
739
            return $this;
15✔
740
        }
741

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

744
        if ($value === null) {
60✔
745
            throw RoundingNecessaryException::decimalScaleTooSmall();
9✔
746
        }
747

748
        return new BigDecimal($value, $scale);
51✔
749
    }
750

751
    #[Override]
752
    public function toInt(): int
753
    {
754
        return $this->toBigInteger()->toInt();
33✔
755
    }
756

757
    #[Override]
758
    public function toFloat(): float
759
    {
760
        return (float) $this->toString();
117✔
761
    }
762

763
    /**
764
     * @return numeric-string
765
     */
766
    #[Override]
767
    public function toString(): string
768
    {
769
        if ($this->scale === 0) {
7,161✔
770
            /** @var numeric-string */
771
            return $this->value;
1,584✔
772
        }
773

774
        $value = DecimalHelper::padUnscaledValue($this->value, $this->scale);
5,622✔
775

776
        /** @phpstan-ignore return.type */
777
        return substr($value, 0, -$this->scale) . '.' . substr($value, -$this->scale);
5,622✔
778
    }
779

780
    /**
781
     * This method is required for serializing the object and SHOULD NOT be accessed directly.
782
     *
783
     * @internal
784
     *
785
     * @return array{value: string, scale: int}
786
     */
787
    public function __serialize(): array
788
    {
789
        return ['value' => $this->value, 'scale' => $this->scale];
3✔
790
    }
791

792
    /**
793
     * This method is only here to allow unserializing the object and cannot be accessed directly.
794
     *
795
     * @internal
796
     *
797
     * @param array{value: string, scale: int} $data
798
     *
799
     * @throws LogicException
800
     */
801
    public function __unserialize(array $data): void
802
    {
803
        /** @phpstan-ignore isset.initializedProperty */
804
        if (isset($this->value)) {
6✔
805
            throw new LogicException('__unserialize() is an internal function, it must not be called directly.');
3✔
806
        }
807

808
        /** @phpstan-ignore deadCode.unreachable */
809
        $this->value = $data['value'];
3✔
810
        $this->scale = $data['scale'];
3✔
811
    }
812

813
    #[Override]
814
    protected static function from(BigNumber $number): static
815
    {
816
        return $number->toBigDecimal();
11,664✔
817
    }
818

819
    /**
820
     * Puts the internal values of the given decimal numbers on the same scale.
821
     *
822
     * @return array{string, string} The scaled integer values of $x and $y.
823
     *
824
     * @pure
825
     */
826
    private function scaleValues(BigDecimal $x, BigDecimal $y): array
827
    {
828
        $a = $x->value;
1,278✔
829
        $b = $y->value;
1,278✔
830

831
        if ($b !== '0' && $x->scale > $y->scale) {
1,278✔
832
            $b .= str_repeat('0', $x->scale - $y->scale);
483✔
833
        } elseif ($a !== '0' && $x->scale < $y->scale) {
828✔
834
            $a .= str_repeat('0', $y->scale - $x->scale);
198✔
835
        }
836

837
        return [$a, $b];
1,278✔
838
    }
839

840
    /**
841
     * @pure
842
     */
843
    private function valueWithMinScale(int $scale): string
844
    {
845
        $value = $this->value;
1,944✔
846

847
        if ($this->value !== '0' && $scale > $this->scale) {
1,944✔
848
            $value .= str_repeat('0', $scale - $this->scale);
1,782✔
849
        }
850

851
        return $value;
1,944✔
852
    }
853

854
    /**
855
     * @pure
856
     */
857
    private function isOneScaleZero(): bool
858
    {
859
        return $this->value === '1' && $this->scale === 0;
2,148✔
860
    }
861
}
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