PluginProbe
Media Cloud Sync / 1.2.6
Media Cloud Sync v1.2.6
1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 All 34 releases
media-cloud-sync / includes / sdk / google / brick / math / src / BigNumber.php

BigNumber.php in Media Cloud Sync 1.2.6, at includes/sdk/google/brick/math/src/BigNumber.php

474 lines 15.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare (strict_types=1);
4 namespace Dudlewebs\WPMCS\Brick\Math;
5
6 use Dudlewebs\WPMCS\Brick\Math\Exception\DivisionByZeroException;
7 use Dudlewebs\WPMCS\Brick\Math\Exception\MathException;
8 use Dudlewebs\WPMCS\Brick\Math\Exception\NumberFormatException;
9 use Dudlewebs\WPMCS\Brick\Math\Exception\RoundingNecessaryException;
10 /**
11 * Common interface for arbitrary-precision rational numbers.
12 *
13 * @psalm-immutable
14 */
15 abstract class BigNumber implements \Serializable, \JsonSerializable
16 {
17 /**
18 * The regular expression used to parse integer, decimal and rational numbers.
19 */
20 private const PARSE_REGEXP = '/^' . '(?<sign>[\-\+])?' . '(?:' . '(?:' . '(?<integral>[0-9]+)?' . '(?<point>\.)?' . '(?<fractional>[0-9]+)?' . '(?:[eE](?<exponent>[\-\+]?[0-9]+))?' . ')|(?:' . '(?<numerator>[0-9]+)' . '\/?' . '(?<denominator>[0-9]+)' . ')' . ')' . '$/';
21 /**
22 * Creates a BigNumber of the given value.
23 *
24 * The concrete return type is dependent on the given value, with the following rules:
25 *
26 * - BigNumber instances are returned as is
27 * - integer numbers are returned as BigInteger
28 * - floating point numbers are converted to a string then parsed as such
29 * - strings containing a `/` character are returned as BigRational
30 * - strings containing a `.` character or using an exponential notation are returned as BigDecimal
31 * - strings containing only digits with an optional leading `+` or `-` sign are returned as BigInteger
32 *
33 * @param BigNumber|int|float|string $value
34 *
35 * @return BigNumber
36 *
37 * @throws NumberFormatException If the format of the number is not valid.
38 * @throws DivisionByZeroException If the value represents a rational number with a denominator of zero.
39 *
40 * @psalm-pure
41 */
42 public static function of($value): BigNumber
43 {
44 if ($value instanceof BigNumber) {
45 return $value;
46 }
47 if (\is_int($value)) {
48 return new BigInteger((string) $value);
49 }
50 /** @psalm-suppress RedundantCastGivenDocblockType We cannot trust the untyped $value here! */
51 $value = \is_float($value) ? self::floatToString($value) : (string) $value;
52 $throw = static function () use ($value): void {
53 throw new NumberFormatException(\sprintf('The given value "%s" does not represent a valid number.', $value));
54 };
55 if (\preg_match(self::PARSE_REGEXP, $value, $matches) !== 1) {
56 $throw();
57 }
58 $getMatch = static function (string $value) use ($matches): ?string {
59 return isset($matches[$value]) && $matches[$value] !== '' ? $matches[$value] : null;
60 };
61 $sign = $getMatch('sign');
62 $numerator = $getMatch('numerator');
63 $denominator = $getMatch('denominator');
64 if ($numerator !== null) {
65 assert($denominator !== null);
66 if ($sign !== null) {
67 $numerator = $sign . $numerator;
68 }
69 $numerator = self::cleanUp($numerator);
70 $denominator = self::cleanUp($denominator);
71 if ($denominator === '0') {
72 throw DivisionByZeroException::denominatorMustNotBeZero();
73 }
74 return new BigRational(new BigInteger($numerator), new BigInteger($denominator), \false);
75 }
76 $point = $getMatch('point');
77 $integral = $getMatch('integral');
78 $fractional = $getMatch('fractional');
79 $exponent = $getMatch('exponent');
80 if ($integral === null && $fractional === null) {
81 $throw();
82 }
83 if ($integral === null) {
84 $integral = '0';
85 }
86 if ($point !== null || $exponent !== null) {
87 $fractional = $fractional ?? '';
88 $exponent = $exponent !== null ? (int) $exponent : 0;
89 if ($exponent === \PHP_INT_MIN || $exponent === \PHP_INT_MAX) {
90 throw new NumberFormatException('Exponent too large.');
91 }
92 $unscaledValue = self::cleanUp(($sign ?? '') . $integral . $fractional);
93 $scale = \strlen($fractional) - $exponent;
94 if ($scale < 0) {
95 if ($unscaledValue !== '0') {
96 $unscaledValue .= \str_repeat('0', -$scale);
97 }
98 $scale = 0;
99 }
100 return new BigDecimal($unscaledValue, $scale);
101 }
102 $integral = self::cleanUp(($sign ?? '') . $integral);
103 return new BigInteger($integral);
104 }
105 /**
106 * Safely converts float to string, avoiding locale-dependent issues.
107 *
108 * @see https://github.com/brick/math/pull/20
109 *
110 * @param float $float
111 *
112 * @return string
113 *
114 * @psalm-pure
115 * @psalm-suppress ImpureFunctionCall
116 */
117 private static function floatToString(float $float): string
118 {
119 $currentLocale = \setlocale(\LC_NUMERIC, '0');
120 \setlocale(\LC_NUMERIC, 'C');
121 $result = (string) $float;
122 \setlocale(\LC_NUMERIC, $currentLocale);
123 return $result;
124 }
125 /**
126 * Proxy method to access protected constructors from sibling classes.
127 *
128 * @internal
129 *
130 * @param mixed ...$args The arguments to the constructor.
131 *
132 * @return static
133 *
134 * @psalm-pure
135 * @psalm-suppress TooManyArguments
136 * @psalm-suppress UnsafeInstantiation
137 */
138 protected static function create(...$args): BigNumber
139 {
140 return new static(...$args);
141 }
142 /**
143 * Returns the minimum of the given values.
144 *
145 * @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible
146 * to an instance of the class this method is called on.
147 *
148 * @return static The minimum value.
149 *
150 * @throws \InvalidArgumentException If no values are given.
151 * @throws MathException If an argument is not valid.
152 *
153 * @psalm-suppress LessSpecificReturnStatement
154 * @psalm-suppress MoreSpecificReturnType
155 * @psalm-pure
156 */
157 public static function min(...$values): BigNumber
158 {
159 $min = null;
160 foreach ($values as $value) {
161 $value = static::of($value);
162 if ($min === null || $value->isLessThan($min)) {
163 $min = $value;
164 }
165 }
166 if ($min === null) {
167 throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
168 }
169 return $min;
170 }
171 /**
172 * Returns the maximum of the given values.
173 *
174 * @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible
175 * to an instance of the class this method is called on.
176 *
177 * @return static The maximum value.
178 *
179 * @throws \InvalidArgumentException If no values are given.
180 * @throws MathException If an argument is not valid.
181 *
182 * @psalm-suppress LessSpecificReturnStatement
183 * @psalm-suppress MoreSpecificReturnType
184 * @psalm-pure
185 */
186 public static function max(...$values): BigNumber
187 {
188 $max = null;
189 foreach ($values as $value) {
190 $value = static::of($value);
191 if ($max === null || $value->isGreaterThan($max)) {
192 $max = $value;
193 }
194 }
195 if ($max === null) {
196 throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
197 }
198 return $max;
199 }
200 /**
201 * Returns the sum of the given values.
202 *
203 * @param BigNumber|int|float|string ...$values The numbers to add. All the numbers need to be convertible
204 * to an instance of the class this method is called on.
205 *
206 * @return static The sum.
207 *
208 * @throws \InvalidArgumentException If no values are given.
209 * @throws MathException If an argument is not valid.
210 *
211 * @psalm-suppress LessSpecificReturnStatement
212 * @psalm-suppress MoreSpecificReturnType
213 * @psalm-pure
214 */
215 public static function sum(...$values): BigNumber
216 {
217 /** @var BigNumber|null $sum */
218 $sum = null;
219 foreach ($values as $value) {
220 $value = static::of($value);
221 $sum = $sum === null ? $value : self::add($sum, $value);
222 }
223 if ($sum === null) {
224 throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
225 }
226 return $sum;
227 }
228 /**
229 * Adds two BigNumber instances in the correct order to avoid a RoundingNecessaryException.
230 *
231 * @todo This could be better resolved by creating an abstract protected method in BigNumber, and leaving to
232 * concrete classes the responsibility to perform the addition themselves or delegate it to the given number,
233 * depending on their ability to perform the operation. This will also require a version bump because we're
234 * potentially breaking custom BigNumber implementations (if any...)
235 *
236 * @param BigNumber $a
237 * @param BigNumber $b
238 *
239 * @return BigNumber
240 *
241 * @psalm-pure
242 */
243 private static function add(BigNumber $a, BigNumber $b): BigNumber
244 {
245 if ($a instanceof BigRational) {
246 return $a->plus($b);
247 }
248 if ($b instanceof BigRational) {
249 return $b->plus($a);
250 }
251 if ($a instanceof BigDecimal) {
252 return $a->plus($b);
253 }
254 if ($b instanceof BigDecimal) {
255 return $b->plus($a);
256 }
257 /** @var BigInteger $a */
258 return $a->plus($b);
259 }
260 /**
261 * Removes optional leading zeros and + sign from the given number.
262 *
263 * @param string $number The number, validated as a non-empty string of digits with optional leading sign.
264 *
265 * @return string
266 *
267 * @psalm-pure
268 */
269 private static function cleanUp(string $number): string
270 {
271 $firstChar = $number[0];
272 if ($firstChar === '+' || $firstChar === '-') {
273 $number = \substr($number, 1);
274 }
275 $number = \ltrim($number, '0');
276 if ($number === '') {
277 return '0';
278 }
279 if ($firstChar === '-') {
280 return '-' . $number;
281 }
282 return $number;
283 }
284 /**
285 * Checks if this number is equal to the given one.
286 *
287 * @param BigNumber|int|float|string $that
288 *
289 * @return bool
290 */
291 public function isEqualTo($that): bool
292 {
293 return $this->compareTo($that) === 0;
294 }
295 /**
296 * Checks if this number is strictly lower than the given one.
297 *
298 * @param BigNumber|int|float|string $that
299 *
300 * @return bool
301 */
302 public function isLessThan($that): bool
303 {
304 return $this->compareTo($that) < 0;
305 }
306 /**
307 * Checks if this number is lower than or equal to the given one.
308 *
309 * @param BigNumber|int|float|string $that
310 *
311 * @return bool
312 */
313 public function isLessThanOrEqualTo($that): bool
314 {
315 return $this->compareTo($that) <= 0;
316 }
317 /**
318 * Checks if this number is strictly greater than the given one.
319 *
320 * @param BigNumber|int|float|string $that
321 *
322 * @return bool
323 */
324 public function isGreaterThan($that): bool
325 {
326 return $this->compareTo($that) > 0;
327 }
328 /**
329 * Checks if this number is greater than or equal to the given one.
330 *
331 * @param BigNumber|int|float|string $that
332 *
333 * @return bool
334 */
335 public function isGreaterThanOrEqualTo($that): bool
336 {
337 return $this->compareTo($that) >= 0;
338 }
339 /**
340 * Checks if this number equals zero.
341 *
342 * @return bool
343 */
344 public function isZero(): bool
345 {
346 return $this->getSign() === 0;
347 }
348 /**
349 * Checks if this number is strictly negative.
350 *
351 * @return bool
352 */
353 public function isNegative(): bool
354 {
355 return $this->getSign() < 0;
356 }
357 /**
358 * Checks if this number is negative or zero.
359 *
360 * @return bool
361 */
362 public function isNegativeOrZero(): bool
363 {
364 return $this->getSign() <= 0;
365 }
366 /**
367 * Checks if this number is strictly positive.
368 *
369 * @return bool
370 */
371 public function isPositive(): bool
372 {
373 return $this->getSign() > 0;
374 }
375 /**
376 * Checks if this number is positive or zero.
377 *
378 * @return bool
379 */
380 public function isPositiveOrZero(): bool
381 {
382 return $this->getSign() >= 0;
383 }
384 /**
385 * Returns the sign of this number.
386 *
387 * @return int -1 if the number is negative, 0 if zero, 1 if positive.
388 */
389 abstract public function getSign(): int;
390 /**
391 * Compares this number to the given one.
392 *
393 * @param BigNumber|int|float|string $that
394 *
395 * @return int [-1,0,1] If `$this` is lower than, equal to, or greater than `$that`.
396 *
397 * @throws MathException If the number is not valid.
398 */
399 abstract public function compareTo($that): int;
400 /**
401 * Converts this number to a BigInteger.
402 *
403 * @return BigInteger The converted number.
404 *
405 * @throws RoundingNecessaryException If this number cannot be converted to a BigInteger without rounding.
406 */
407 abstract public function toBigInteger(): BigInteger;
408 /**
409 * Converts this number to a BigDecimal.
410 *
411 * @return BigDecimal The converted number.
412 *
413 * @throws RoundingNecessaryException If this number cannot be converted to a BigDecimal without rounding.
414 */
415 abstract public function toBigDecimal(): BigDecimal;
416 /**
417 * Converts this number to a BigRational.
418 *
419 * @return BigRational The converted number.
420 */
421 abstract public function toBigRational(): BigRational;
422 /**
423 * Converts this number to a BigDecimal with the given scale, using rounding if necessary.
424 *
425 * @param int $scale The scale of the resulting `BigDecimal`.
426 * @param int $roundingMode A `RoundingMode` constant.
427 *
428 * @return BigDecimal
429 *
430 * @throws RoundingNecessaryException If this number cannot be converted to the given scale without rounding.
431 * This only applies when RoundingMode::UNNECESSARY is used.
432 */
433 abstract public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY): BigDecimal;
434 /**
435 * Returns the exact value of this number as a native integer.
436 *
437 * If this number cannot be converted to a native integer without losing precision, an exception is thrown.
438 * Note that the acceptable range for an integer depends on the platform and differs for 32-bit and 64-bit.
439 *
440 * @return int The converted value.
441 *
442 * @throws MathException If this number cannot be exactly converted to a native integer.
443 */
444 abstract public function toInt(): int;
445 /**
446 * Returns an approximation of this number as a floating-point value.
447 *
448 * Note that this method can discard information as the precision of a floating-point value
449 * is inherently limited.
450 *
451 * If the number is greater than the largest representable floating point number, positive infinity is returned.
452 * If the number is less than the smallest representable floating point number, negative infinity is returned.
453 *
454 * @return float The converted value.
455 */
456 abstract public function toFloat(): float;
457 /**
458 * Returns a string representation of this number.
459 *
460 * The output of this method can be parsed by the `of()` factory method;
461 * this will yield an object equal to this one, without any information loss.
462 *
463 * @return string
464 */
465 abstract public function __toString(): string;
466 /**
467 * {@inheritdoc}
468 */
469 public function jsonSerialize(): string
470 {
471 return $this->__toString();
472 }
473 }
474