• 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

97.87
/src/BigRational.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\MathException;
9
use Brick\Math\Exception\RoundingNecessaryException;
10
use Brick\Math\Internal\DecimalHelper;
11
use LogicException;
12
use Override;
13

14
use function is_finite;
15
use function max;
16
use function min;
17
use function strlen;
18
use function substr;
19

20
/**
21
 * An arbitrarily large rational number.
22
 *
23
 * This class is immutable.
24
 *
25
 * Fractions are automatically simplified to lowest terms. For example, `2/4` becomes `1/2`.
26
 * The denominator is always strictly positive; the sign is carried by the numerator.
27
 */
28
final readonly class BigRational extends BigNumber
29
{
30
    /**
31
     * The numerator.
32
     */
33
    private BigInteger $numerator;
34

35
    /**
36
     * The denominator. Always strictly positive.
37
     */
38
    private BigInteger $denominator;
39

40
    /**
41
     * Protected constructor. Use a factory method to obtain an instance.
42
     *
43
     * @param BigInteger $numerator        The numerator.
44
     * @param BigInteger $denominator      The denominator.
45
     * @param bool       $checkDenominator Whether to check the denominator for negative and zero.
46
     *
47
     * @throws DivisionByZeroException If the denominator is zero.
48
     *
49
     * @pure
50
     */
51
    protected function __construct(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator, bool $simplify)
52
    {
53
        if ($checkDenominator) {
2,421✔
54
            if ($denominator->isZero()) {
162✔
55
                throw DivisionByZeroException::zeroDenominator();
3✔
56
            }
57

58
            if ($denominator->isNegative()) {
159✔
59
                $numerator = $numerator->negated();
54✔
60
                $denominator = $denominator->negated();
54✔
61
            }
62
        }
63

64
        if ($simplify) {
2,418✔
65
            $gcd = $numerator->gcd($denominator);
2,202✔
66

67
            $numerator = $numerator->quotient($gcd);
2,202✔
68
            $denominator = $denominator->quotient($gcd);
2,202✔
69
        }
70

71
        $this->numerator = $numerator;
2,418✔
72
        $this->denominator = $denominator;
2,418✔
73
    }
74

75
    /**
76
     * Creates a BigRational out of a numerator and a denominator.
77
     *
78
     * If the denominator is negative, the signs of both the numerator and the denominator
79
     * will be inverted to ensure that the denominator is always positive.
80
     *
81
     * @param BigNumber|int|string $numerator   The numerator. Must be convertible to a BigInteger.
82
     * @param BigNumber|int|string $denominator The denominator. Must be convertible to a BigInteger.
83
     *
84
     * @throws MathException           If an argument is not valid, or is not convertible to a BigInteger.
85
     * @throws DivisionByZeroException If the denominator is zero.
86
     *
87
     * @pure
88
     */
89
    public static function ofFraction(
90
        BigNumber|int|string $numerator,
91
        BigNumber|int|string $denominator,
92
    ): BigRational {
93
        $numerator = BigInteger::of($numerator);
81✔
94
        $denominator = BigInteger::of($denominator);
81✔
95

96
        return new BigRational($numerator, $denominator, true, true);
81✔
97
    }
98

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

109
        if ($zero === null) {
3✔
110
            $zero = new BigRational(BigInteger::zero(), BigInteger::one(), false, false);
3✔
111
        }
112

113
        return $zero;
3✔
114
    }
115

116
    /**
117
     * Returns a BigRational representing one.
118
     *
119
     * @pure
120
     */
121
    public static function one(): BigRational
122
    {
123
        /** @var BigRational|null $one */
124
        static $one;
24✔
125

126
        if ($one === null) {
24✔
127
            $one = new BigRational(BigInteger::one(), BigInteger::one(), false, false);
3✔
128
        }
129

130
        return $one;
24✔
131
    }
132

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

143
        if ($ten === null) {
3✔
144
            $ten = new BigRational(BigInteger::ten(), BigInteger::one(), false, false);
3✔
145
        }
146

147
        return $ten;
3✔
148
    }
149

150
    /**
151
     * @pure
152
     */
153
    public function getNumerator(): BigInteger
154
    {
155
        return $this->numerator;
189✔
156
    }
157

158
    /**
159
     * @pure
160
     */
161
    public function getDenominator(): BigInteger
162
    {
163
        return $this->denominator;
189✔
164
    }
165

166
    /**
167
     * Returns the integral part of this rational number.
168
     *
169
     * Examples:
170
     *
171
     * - `7/3` returns `2` (since 7/3 = 2 + 1/3)
172
     * - `-7/3` returns `-2` (since -7/3 = -2 + (-1/3))
173
     *
174
     * The following identity holds: `$r->isEqualTo($r->getFractionalPart()->plus($r->getIntegralPart()))`.
175
     *
176
     * @pure
177
     */
178
    public function getIntegralPart(): BigInteger
179
    {
180
        return $this->numerator->quotient($this->denominator);
195✔
181
    }
182

183
    /**
184
     * Returns the fractional part of this rational number.
185
     *
186
     * Examples:
187
     *
188
     * - `7/3` returns `1/3` (since 7/3 = 2 + 1/3)
189
     * - `-7/3` returns `-1/3` (since -7/3 = -2 + (-1/3))
190
     *
191
     * The following identity holds: `$r->isEqualTo($r->getFractionalPart()->plus($r->getIntegralPart()))`.
192
     *
193
     * @pure
194
     */
195
    public function getFractionalPart(): BigRational
196
    {
197
        return new BigRational($this->numerator->remainder($this->denominator), $this->denominator, false, false);
57✔
198
    }
199

200
    /**
201
     * Returns the sum of this number and the given one.
202
     *
203
     * @param BigNumber|int|string $that The number to add.
204
     *
205
     * @throws MathException If the number is not valid.
206
     *
207
     * @pure
208
     */
209
    public function plus(BigNumber|int|string $that): BigRational
210
    {
211
        $that = BigRational::of($that);
123✔
212

213
        if ($that->isZero()) {
123✔
214
            return $this;
9✔
215
        }
216

217
        if ($this->isZero()) {
114✔
218
            return $that;
15✔
219
        }
220

221
        $numerator = $this->numerator->multipliedBy($that->denominator);
105✔
222
        $numerator = $numerator->plus($that->numerator->multipliedBy($this->denominator));
105✔
223
        $denominator = $this->denominator->multipliedBy($that->denominator);
105✔
224

225
        return new BigRational($numerator, $denominator, false, true);
105✔
226
    }
227

228
    /**
229
     * Returns the difference of this number and the given one.
230
     *
231
     * @param BigNumber|int|string $that The number to subtract.
232
     *
233
     * @throws MathException If the number is not valid.
234
     *
235
     * @pure
236
     */
237
    public function minus(BigNumber|int|string $that): BigRational
238
    {
239
        $that = BigRational::of($that);
15✔
240

241
        if ($that->isZero()) {
15✔
242
            return $this;
×
243
        }
244

245
        if ($this->isZero()) {
15✔
246
            return $that->negated();
×
247
        }
248

249
        $numerator = $this->numerator->multipliedBy($that->denominator);
15✔
250
        $numerator = $numerator->minus($that->numerator->multipliedBy($this->denominator));
15✔
251
        $denominator = $this->denominator->multipliedBy($that->denominator);
15✔
252

253
        return new BigRational($numerator, $denominator, false, true);
15✔
254
    }
255

256
    /**
257
     * Returns the product of this number and the given one.
258
     *
259
     * @param BigNumber|int|string $that The multiplier.
260
     *
261
     * @throws MathException If the multiplier is not valid.
262
     *
263
     * @pure
264
     */
265
    public function multipliedBy(BigNumber|int|string $that): BigRational
266
    {
267
        $that = BigRational::of($that);
21✔
268

269
        if ($that->isZero() || $this->isZero()) {
21✔
270
            return BigRational::zero();
×
271
        }
272

273
        $numerator = $this->numerator->multipliedBy($that->numerator);
21✔
274
        $denominator = $this->denominator->multipliedBy($that->denominator);
21✔
275

276
        return new BigRational($numerator, $denominator, false, true);
21✔
277
    }
278

279
    /**
280
     * Returns the result of the division of this number by the given one.
281
     *
282
     * @param BigNumber|int|string $that The divisor.
283
     *
284
     * @throws MathException           If the divisor is not valid.
285
     * @throws DivisionByZeroException If the divisor is zero.
286
     *
287
     * @pure
288
     */
289
    public function dividedBy(BigNumber|int|string $that): BigRational
290
    {
291
        $that = BigRational::of($that);
24✔
292

293
        if ($that->isZero()) {
24✔
294
            throw DivisionByZeroException::divisionByZero();
3✔
295
        }
296

297
        $numerator = $this->numerator->multipliedBy($that->denominator);
21✔
298
        $denominator = $this->denominator->multipliedBy($that->numerator);
21✔
299

300
        return new BigRational($numerator, $denominator, true, true);
21✔
301
    }
302

303
    /**
304
     * Returns this number exponentiated to the given value.
305
     *
306
     * Unlike BigInteger and BigDecimal, BigRational supports negative exponents:
307
     * the result is the reciprocal raised to the absolute value of the exponent.
308
     *
309
     * @throws DivisionByZeroException If the exponent is negative and this number is zero.
310
     *
311
     * @pure
312
     */
313
    public function power(int $exponent): BigRational
314
    {
315
        if ($exponent === 0) {
159✔
316
            return BigRational::one();
21✔
317
        }
318

319
        if ($exponent === 1) {
138✔
320
            return $this;
36✔
321
        }
322

323
        if ($exponent < 0) {
117✔
324
            return $this->reciprocal()->power(-$exponent);
51✔
325
        }
326

327
        return new BigRational(
93✔
328
            $this->numerator->power($exponent),
93✔
329
            $this->denominator->power($exponent),
93✔
330
            false,
93✔
331
            false,
93✔
332
        );
93✔
333
    }
334

335
    /**
336
     * Returns the reciprocal of this BigRational.
337
     *
338
     * The reciprocal has the numerator and denominator swapped.
339
     *
340
     * @throws DivisionByZeroException If the numerator is zero.
341
     *
342
     * @pure
343
     */
344
    public function reciprocal(): BigRational
345
    {
346
        if ($this->isZero()) {
72✔
347
            throw DivisionByZeroException::reciprocalOfZero();
12✔
348
        }
349

350
        return new BigRational($this->denominator, $this->numerator, true, false);
60✔
351
    }
352

353
    #[Override]
354
    public function negated(): static
355
    {
356
        return new BigRational($this->numerator->negated(), $this->denominator, false, false);
30✔
357
    }
358

359
    #[Override]
360
    public function compareTo(BigNumber|int|string $that): int
361
    {
362
        $that = BigRational::of($that);
864✔
363

364
        if ($this->denominator->isEqualTo($that->denominator)) {
864✔
365
            return $this->numerator->compareTo($that->numerator);
369✔
366
        }
367

368
        return $this->numerator
528✔
369
            ->multipliedBy($that->denominator)
528✔
370
            ->compareTo($that->numerator->multipliedBy($this->denominator));
528✔
371
    }
372

373
    #[Override]
374
    public function getSign(): int
375
    {
376
        return $this->numerator->getSign();
489✔
377
    }
378

379
    #[Override]
380
    public function toBigInteger(): BigInteger
381
    {
382
        if ($this->denominator->isEqualTo(1)) {
111✔
383
            return $this->numerator;
78✔
384
        }
385

386
        throw RoundingNecessaryException::rationalNotConvertibleToInteger();
33✔
387
    }
388

389
    #[Override]
390
    public function toBigDecimal(): BigDecimal
391
    {
392
        $scale = DecimalHelper::computeScaleFromReducedFractionDenominator($this->denominator->toString());
483✔
393

394
        if ($scale === null) {
483✔
395
            throw RoundingNecessaryException::rationalNotConvertibleToDecimal();
186✔
396
        }
397

398
        return $this->numerator->toBigDecimal()->dividedBy($this->denominator, $scale)->strippedOfTrailingZeros();
297✔
399
    }
400

401
    #[Override]
402
    public function toBigRational(): BigRational
403
    {
404
        return $this;
1,680✔
405
    }
406

407
    #[Override]
408
    public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigDecimal
409
    {
410
        return $this->numerator->toBigDecimal()->dividedBy($this->denominator, $scale, $roundingMode);
21✔
411
    }
412

413
    #[Override]
414
    public function toInt(): int
415
    {
416
        return $this->toBigInteger()->toInt();
42✔
417
    }
418

419
    #[Override]
420
    public function toFloat(): float
421
    {
422
        $numeratorFloat = $this->numerator->toFloat();
162✔
423
        $denominatorFloat = $this->denominator->toFloat();
162✔
424

425
        if (is_finite($numeratorFloat) && is_finite($denominatorFloat)) {
162✔
426
            return $numeratorFloat / $denominatorFloat;
66✔
427
        }
428

429
        // At least one side overflows to INF; use a decimal approximation instead.
430
        // We need ~17 significant digits for double precision (we use 20 for some margin). Since $scale controls
431
        // decimal places (not significant digits), we subtract the estimated order of magnitude so that large results
432
        // use fewer decimal places and small results use more (to look past leading zeros). Clamped to [0, 350] as
433
        // doubles range from e-324 to e308 (350 ≈ 324 + 20 significant digits + margin).
434
        $magnitude = strlen($this->numerator->abs()->toString()) - strlen($this->denominator->toString());
96✔
435
        $scale = min(350, max(0, 20 - $magnitude));
96✔
436

437
        return $this->numerator
96✔
438
            ->toBigDecimal()
96✔
439
            ->dividedBy($this->denominator, $scale, RoundingMode::HalfEven)
96✔
440
            ->toFloat();
96✔
441
    }
442

443
    #[Override]
444
    public function toString(): string
445
    {
446
        $numerator = $this->numerator->toString();
570✔
447
        $denominator = $this->denominator->toString();
570✔
448

449
        if ($denominator === '1') {
570✔
450
            return $numerator;
162✔
451
        }
452

453
        return $numerator . '/' . $denominator;
408✔
454
    }
455

456
    /**
457
     * Returns the decimal representation of this rational number, with repeating decimals in parentheses.
458
     *
459
     * WARNING: This method is unbounded.
460
     *          The length of the repeating decimal period can be as large as `denominator - 1`.
461
     *          For fractions with large denominators, this method can use excessive memory and CPU time.
462
     *          For example, `1/100019` has a repeating period of 100,018 digits.
463
     *
464
     * Examples:
465
     *
466
     * - `10/3` returns `3.(3)`
467
     * - `171/70` returns `2.4(428571)`
468
     * - `1/2` returns `0.5`
469
     *
470
     * @pure
471
     */
472
    public function toRepeatingDecimalString(): string
473
    {
474
        if ($this->isZero()) {
72✔
475
            return '0';
3✔
476
        }
477

478
        $sign = $this->numerator->isNegative() ? '-' : '';
69✔
479
        $numerator = $this->numerator->abs();
69✔
480
        $denominator = $this->denominator;
69✔
481

482
        $integral = $numerator->quotient($denominator);
69✔
483
        $remainder = $numerator->remainder($denominator);
69✔
484

485
        $integralString = $integral->toString();
69✔
486

487
        if ($remainder->isZero()) {
69✔
488
            return $sign . $integralString;
3✔
489
        }
490

491
        $digits = '';
66✔
492
        $remainderPositions = [];
66✔
493
        $index = 0;
66✔
494

495
        while (! $remainder->isZero()) {
66✔
496
            $remainderString = $remainder->toString();
66✔
497

498
            if (isset($remainderPositions[$remainderString])) {
66✔
499
                $repeatIndex = $remainderPositions[$remainderString];
51✔
500
                $nonRepeating = substr($digits, 0, $repeatIndex);
51✔
501
                $repeating = substr($digits, $repeatIndex);
51✔
502

503
                return $sign . $integralString . '.' . $nonRepeating . '(' . $repeating . ')';
51✔
504
            }
505

506
            $remainderPositions[$remainderString] = $index;
66✔
507
            $remainder = $remainder->multipliedBy(10);
66✔
508

509
            $digits .= $remainder->quotient($denominator)->toString();
66✔
510
            $remainder = $remainder->remainder($denominator);
66✔
511
            $index++;
66✔
512
        }
513

514
        return $sign . $integralString . '.' . $digits;
15✔
515
    }
516

517
    /**
518
     * This method is required for serializing the object and SHOULD NOT be accessed directly.
519
     *
520
     * @internal
521
     *
522
     * @return array{numerator: BigInteger, denominator: BigInteger}
523
     */
524
    public function __serialize(): array
525
    {
526
        return ['numerator' => $this->numerator, 'denominator' => $this->denominator];
3✔
527
    }
528

529
    /**
530
     * This method is only here to allow unserializing the object and cannot be accessed directly.
531
     *
532
     * @internal
533
     *
534
     * @param array{numerator: BigInteger, denominator: BigInteger} $data
535
     *
536
     * @throws LogicException
537
     */
538
    public function __unserialize(array $data): void
539
    {
540
        /** @phpstan-ignore isset.initializedProperty */
541
        if (isset($this->numerator)) {
6✔
542
            throw new LogicException('__unserialize() is an internal function, it must not be called directly.');
3✔
543
        }
544

545
        /** @phpstan-ignore deadCode.unreachable */
546
        $this->numerator = $data['numerator'];
3✔
547
        $this->denominator = $data['denominator'];
3✔
548
    }
549

550
    #[Override]
551
    protected static function from(BigNumber $number): static
552
    {
553
        return $number->toBigRational();
2,280✔
554
    }
555
}
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