Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 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\NumberFormatException;
10
use Brick\Math\Exception\RoundingNecessaryException;
11
 
12
/**
13
 * An arbitrarily large rational number.
14
 *
15
 * This class is immutable.
16
 *
17
 * @psalm-immutable
18
 */
19
final class BigRational extends BigNumber
20
{
21
    /**
22
     * The numerator.
23
     */
24
    private BigInteger $numerator;
25
 
26
    /**
27
     * The denominator. Always strictly positive.
28
     */
29
    private BigInteger $denominator;
30
 
31
    /**
32
     * Protected constructor. Use a factory method to obtain an instance.
33
     *
34
     * @param BigInteger $numerator        The numerator.
35
     * @param BigInteger $denominator      The denominator.
36
     * @param bool       $checkDenominator Whether to check the denominator for negative and zero.
37
     *
38
     * @throws DivisionByZeroException If the denominator is zero.
39
     */
40
    protected function __construct(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator)
41
    {
42
        if ($checkDenominator) {
43
            if ($denominator->isZero()) {
44
                throw DivisionByZeroException::denominatorMustNotBeZero();
45
            }
46
 
47
            if ($denominator->isNegative()) {
48
                $numerator   = $numerator->negated();
49
                $denominator = $denominator->negated();
50
            }
51
        }
52
 
53
        $this->numerator   = $numerator;
54
        $this->denominator = $denominator;
55
    }
56
 
57
    /**
58
     * Creates a BigRational of the given value.
59
     *
60
     * @throws MathException If the value cannot be converted to a BigRational.
61
     *
62
     * @psalm-pure
63
     */
399 lars 64
    public static function of(BigNumber|int|float|string $value) : BigRational
148 lars 65
    {
66
        return parent::of($value)->toBigRational();
67
    }
68
 
69
    /**
70
     * Creates a BigRational out of a numerator and a denominator.
71
     *
72
     * If the denominator is negative, the signs of both the numerator and the denominator
73
     * will be inverted to ensure that the denominator is always positive.
74
     *
75
     * @param BigNumber|int|float|string $numerator   The numerator. Must be convertible to a BigInteger.
76
     * @param BigNumber|int|float|string $denominator The denominator. Must be convertible to a BigInteger.
77
     *
78
     * @throws NumberFormatException      If an argument does not represent a valid number.
79
     * @throws RoundingNecessaryException If an argument represents a non-integer number.
80
     * @throws DivisionByZeroException    If the denominator is zero.
81
     *
82
     * @psalm-pure
83
     */
399 lars 84
    public static function nd(
85
        BigNumber|int|float|string $numerator,
86
        BigNumber|int|float|string $denominator,
87
    ) : BigRational {
148 lars 88
        $numerator   = BigInteger::of($numerator);
89
        $denominator = BigInteger::of($denominator);
90
 
91
        return new BigRational($numerator, $denominator, true);
92
    }
93
 
94
    /**
95
     * Returns a BigRational representing zero.
96
     *
97
     * @psalm-pure
98
     */
99
    public static function zero() : BigRational
100
    {
101
        /**
102
         * @psalm-suppress ImpureStaticVariable
103
         * @var BigRational|null $zero
104
         */
105
        static $zero;
106
 
107
        if ($zero === null) {
108
            $zero = new BigRational(BigInteger::zero(), BigInteger::one(), false);
109
        }
110
 
111
        return $zero;
112
    }
113
 
114
    /**
115
     * Returns a BigRational representing one.
116
     *
117
     * @psalm-pure
118
     */
119
    public static function one() : BigRational
120
    {
121
        /**
122
         * @psalm-suppress ImpureStaticVariable
123
         * @var BigRational|null $one
124
         */
125
        static $one;
126
 
127
        if ($one === null) {
128
            $one = new BigRational(BigInteger::one(), BigInteger::one(), false);
129
        }
130
 
131
        return $one;
132
    }
133
 
134
    /**
135
     * Returns a BigRational representing ten.
136
     *
137
     * @psalm-pure
138
     */
139
    public static function ten() : BigRational
140
    {
141
        /**
142
         * @psalm-suppress ImpureStaticVariable
143
         * @var BigRational|null $ten
144
         */
145
        static $ten;
146
 
147
        if ($ten === null) {
148
            $ten = new BigRational(BigInteger::ten(), BigInteger::one(), false);
149
        }
150
 
151
        return $ten;
152
    }
153
 
154
    public function getNumerator() : BigInteger
155
    {
156
        return $this->numerator;
157
    }
158
 
159
    public function getDenominator() : BigInteger
160
    {
161
        return $this->denominator;
162
    }
163
 
164
    /**
165
     * Returns the quotient of the division of the numerator by the denominator.
166
     */
167
    public function quotient() : BigInteger
168
    {
169
        return $this->numerator->quotient($this->denominator);
170
    }
171
 
172
    /**
173
     * Returns the remainder of the division of the numerator by the denominator.
174
     */
175
    public function remainder() : BigInteger
176
    {
177
        return $this->numerator->remainder($this->denominator);
178
    }
179
 
180
    /**
181
     * Returns the quotient and remainder of the division of the numerator by the denominator.
182
     *
183
     * @return BigInteger[]
184
     */
185
    public function quotientAndRemainder() : array
186
    {
187
        return $this->numerator->quotientAndRemainder($this->denominator);
188
    }
189
 
190
    /**
191
     * Returns the sum of this number and the given one.
192
     *
193
     * @param BigNumber|int|float|string $that The number to add.
194
     *
195
     * @throws MathException If the number is not valid.
196
     */
399 lars 197
    public function plus(BigNumber|int|float|string $that) : BigRational
148 lars 198
    {
199
        $that = BigRational::of($that);
200
 
201
        $numerator   = $this->numerator->multipliedBy($that->denominator);
202
        $numerator   = $numerator->plus($that->numerator->multipliedBy($this->denominator));
203
        $denominator = $this->denominator->multipliedBy($that->denominator);
204
 
205
        return new BigRational($numerator, $denominator, false);
206
    }
207
 
208
    /**
209
     * Returns the difference of this number and the given one.
210
     *
211
     * @param BigNumber|int|float|string $that The number to subtract.
212
     *
213
     * @throws MathException If the number is not valid.
214
     */
399 lars 215
    public function minus(BigNumber|int|float|string $that) : BigRational
148 lars 216
    {
217
        $that = BigRational::of($that);
218
 
219
        $numerator   = $this->numerator->multipliedBy($that->denominator);
220
        $numerator   = $numerator->minus($that->numerator->multipliedBy($this->denominator));
221
        $denominator = $this->denominator->multipliedBy($that->denominator);
222
 
223
        return new BigRational($numerator, $denominator, false);
224
    }
225
 
226
    /**
227
     * Returns the product of this number and the given one.
228
     *
229
     * @param BigNumber|int|float|string $that The multiplier.
230
     *
231
     * @throws MathException If the multiplier is not a valid number.
232
     */
399 lars 233
    public function multipliedBy(BigNumber|int|float|string $that) : BigRational
148 lars 234
    {
235
        $that = BigRational::of($that);
236
 
237
        $numerator   = $this->numerator->multipliedBy($that->numerator);
238
        $denominator = $this->denominator->multipliedBy($that->denominator);
239
 
240
        return new BigRational($numerator, $denominator, false);
241
    }
242
 
243
    /**
244
     * Returns the result of the division of this number by the given one.
245
     *
246
     * @param BigNumber|int|float|string $that The divisor.
247
     *
248
     * @throws MathException If the divisor is not a valid number, or is zero.
249
     */
399 lars 250
    public function dividedBy(BigNumber|int|float|string $that) : BigRational
148 lars 251
    {
252
        $that = BigRational::of($that);
253
 
254
        $numerator   = $this->numerator->multipliedBy($that->denominator);
255
        $denominator = $this->denominator->multipliedBy($that->numerator);
256
 
257
        return new BigRational($numerator, $denominator, true);
258
    }
259
 
260
    /**
261
     * Returns this number exponentiated to the given value.
262
     *
263
     * @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000.
264
     */
265
    public function power(int $exponent) : BigRational
266
    {
267
        if ($exponent === 0) {
268
            $one = BigInteger::one();
269
 
270
            return new BigRational($one, $one, false);
271
        }
272
 
273
        if ($exponent === 1) {
274
            return $this;
275
        }
276
 
277
        return new BigRational(
278
            $this->numerator->power($exponent),
279
            $this->denominator->power($exponent),
280
            false
281
        );
282
    }
283
 
284
    /**
285
     * Returns the reciprocal of this BigRational.
286
     *
287
     * The reciprocal has the numerator and denominator swapped.
288
     *
289
     * @throws DivisionByZeroException If the numerator is zero.
290
     */
291
    public function reciprocal() : BigRational
292
    {
293
        return new BigRational($this->denominator, $this->numerator, true);
294
    }
295
 
296
    /**
297
     * Returns the absolute value of this BigRational.
298
     */
299
    public function abs() : BigRational
300
    {
301
        return new BigRational($this->numerator->abs(), $this->denominator, false);
302
    }
303
 
304
    /**
305
     * Returns the negated value of this BigRational.
306
     */
307
    public function negated() : BigRational
308
    {
309
        return new BigRational($this->numerator->negated(), $this->denominator, false);
310
    }
311
 
312
    /**
313
     * Returns the simplified value of this BigRational.
314
     */
315
    public function simplified() : BigRational
316
    {
317
        $gcd = $this->numerator->gcd($this->denominator);
318
 
319
        $numerator = $this->numerator->quotient($gcd);
320
        $denominator = $this->denominator->quotient($gcd);
321
 
322
        return new BigRational($numerator, $denominator, false);
323
    }
324
 
399 lars 325
    public function compareTo(BigNumber|int|float|string $that) : int
148 lars 326
    {
327
        return $this->minus($that)->getSign();
328
    }
329
 
330
    public function getSign() : int
331
    {
332
        return $this->numerator->getSign();
333
    }
334
 
335
    public function toBigInteger() : BigInteger
336
    {
337
        $simplified = $this->simplified();
338
 
339
        if (! $simplified->denominator->isEqualTo(1)) {
340
            throw new RoundingNecessaryException('This rational number cannot be represented as an integer value without rounding.');
341
        }
342
 
343
        return $simplified->numerator;
344
    }
345
 
346
    public function toBigDecimal() : BigDecimal
347
    {
348
        return $this->numerator->toBigDecimal()->exactlyDividedBy($this->denominator);
349
    }
350
 
351
    public function toBigRational() : BigRational
352
    {
353
        return $this;
354
    }
355
 
356
    public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
357
    {
358
        return $this->numerator->toBigDecimal()->dividedBy($this->denominator, $scale, $roundingMode);
359
    }
360
 
361
    public function toInt() : int
362
    {
363
        return $this->toBigInteger()->toInt();
364
    }
365
 
366
    public function toFloat() : float
367
    {
368
        $simplified = $this->simplified();
369
        return $simplified->numerator->toFloat() / $simplified->denominator->toFloat();
370
    }
371
 
372
    public function __toString() : string
373
    {
374
        $numerator   = (string) $this->numerator;
375
        $denominator = (string) $this->denominator;
376
 
377
        if ($denominator === '1') {
378
            return $numerator;
379
        }
380
 
381
        return $this->numerator . '/' . $this->denominator;
382
    }
383
 
384
    /**
385
     * This method is required for serializing the object and SHOULD NOT be accessed directly.
386
     *
387
     * @internal
388
     *
389
     * @return array{numerator: BigInteger, denominator: BigInteger}
390
     */
391
    public function __serialize(): array
392
    {
393
        return ['numerator' => $this->numerator, 'denominator' => $this->denominator];
394
    }
395
 
396
    /**
397
     * This method is only here to allow unserializing the object and cannot be accessed directly.
398
     *
399
     * @internal
400
     * @psalm-suppress RedundantPropertyInitializationCheck
401
     *
402
     * @param array{numerator: BigInteger, denominator: BigInteger} $data
403
     *
404
     * @throws \LogicException
405
     */
406
    public function __unserialize(array $data): void
407
    {
408
        if (isset($this->numerator)) {
409
            throw new \LogicException('__unserialize() is an internal function, it must not be called directly.');
410
        }
411
 
412
        $this->numerator = $data['numerator'];
413
        $this->denominator = $data['denominator'];
414
    }
415
 
416
    /**
417
     * This method is required by interface Serializable and SHOULD NOT be accessed directly.
418
     *
419
     * @internal
420
     */
421
    public function serialize() : string
422
    {
423
        return $this->numerator . '/' . $this->denominator;
424
    }
425
 
426
    /**
427
     * This method is only here to implement interface Serializable and cannot be accessed directly.
428
     *
429
     * @internal
430
     * @psalm-suppress RedundantPropertyInitializationCheck
431
     *
432
     * @throws \LogicException
433
     */
434
    public function unserialize($value) : void
435
    {
436
        if (isset($this->numerator)) {
437
            throw new \LogicException('unserialize() is an internal function, it must not be called directly.');
438
        }
439
 
440
        [$numerator, $denominator] = \explode('/', $value);
441
 
442
        $this->numerator   = BigInteger::of($numerator);
443
        $this->denominator = BigInteger::of($denominator);
444
    }
445
}