BigRational.php 12 KB

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