BigNumber.php 16 KB

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