PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.4
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.4
1.10.19 1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 1.9.14 All 163 releases
woocommerce-pos / vendor / brick / math / src / BigNumber.php

BigNumber.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.10.4, at vendor/brick/math/src/BigNumber.php

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