BigRational.php 13 KB

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