BigRational.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  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 readonly BigInteger $numerator;
  21. /**
  22. * The denominator. Always strictly positive.
  23. */
  24. private readonly 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. * @psalm-pure
  50. */
  51. protected static function from(BigNumber $number): static
  52. {
  53. return $number->toBigRational();
  54. }
  55. /**
  56. * Creates a BigRational out of a numerator and a denominator.
  57. *
  58. * If the denominator is negative, the signs of both the numerator and the denominator
  59. * will be inverted to ensure that the denominator is always positive.
  60. *
  61. * @param BigNumber|int|float|string $numerator The numerator. Must be convertible to a BigInteger.
  62. * @param BigNumber|int|float|string $denominator The denominator. Must be convertible to a BigInteger.
  63. *
  64. * @throws NumberFormatException If an argument does not represent a valid number.
  65. * @throws RoundingNecessaryException If an argument represents a non-integer number.
  66. * @throws DivisionByZeroException If the denominator is zero.
  67. *
  68. * @psalm-pure
  69. */
  70. public static function nd(
  71. BigNumber|int|float|string $numerator,
  72. BigNumber|int|float|string $denominator,
  73. ) : BigRational {
  74. $numerator = BigInteger::of($numerator);
  75. $denominator = BigInteger::of($denominator);
  76. return new BigRational($numerator, $denominator, true);
  77. }
  78. /**
  79. * Returns a BigRational representing zero.
  80. *
  81. * @psalm-pure
  82. */
  83. public static function zero() : BigRational
  84. {
  85. /**
  86. * @psalm-suppress ImpureStaticVariable
  87. * @var BigRational|null $zero
  88. */
  89. static $zero;
  90. if ($zero === null) {
  91. $zero = new BigRational(BigInteger::zero(), BigInteger::one(), false);
  92. }
  93. return $zero;
  94. }
  95. /**
  96. * Returns a BigRational representing one.
  97. *
  98. * @psalm-pure
  99. */
  100. public static function one() : BigRational
  101. {
  102. /**
  103. * @psalm-suppress ImpureStaticVariable
  104. * @var BigRational|null $one
  105. */
  106. static $one;
  107. if ($one === null) {
  108. $one = new BigRational(BigInteger::one(), BigInteger::one(), false);
  109. }
  110. return $one;
  111. }
  112. /**
  113. * Returns a BigRational representing ten.
  114. *
  115. * @psalm-pure
  116. */
  117. public static function ten() : BigRational
  118. {
  119. /**
  120. * @psalm-suppress ImpureStaticVariable
  121. * @var BigRational|null $ten
  122. */
  123. static $ten;
  124. if ($ten === null) {
  125. $ten = new BigRational(BigInteger::ten(), BigInteger::one(), false);
  126. }
  127. return $ten;
  128. }
  129. public function getNumerator() : BigInteger
  130. {
  131. return $this->numerator;
  132. }
  133. public function getDenominator() : BigInteger
  134. {
  135. return $this->denominator;
  136. }
  137. /**
  138. * Returns the quotient of the division of the numerator by the denominator.
  139. */
  140. public function quotient() : BigInteger
  141. {
  142. return $this->numerator->quotient($this->denominator);
  143. }
  144. /**
  145. * Returns the remainder of the division of the numerator by the denominator.
  146. */
  147. public function remainder() : BigInteger
  148. {
  149. return $this->numerator->remainder($this->denominator);
  150. }
  151. /**
  152. * Returns the quotient and remainder of the division of the numerator by the denominator.
  153. *
  154. * @return BigInteger[]
  155. *
  156. * @psalm-return array{BigInteger, BigInteger}
  157. */
  158. public function quotientAndRemainder() : array
  159. {
  160. return $this->numerator->quotientAndRemainder($this->denominator);
  161. }
  162. /**
  163. * Returns the sum of this number and the given one.
  164. *
  165. * @param BigNumber|int|float|string $that The number to add.
  166. *
  167. * @throws MathException If the number is not valid.
  168. */
  169. public function plus(BigNumber|int|float|string $that) : BigRational
  170. {
  171. $that = BigRational::of($that);
  172. $numerator = $this->numerator->multipliedBy($that->denominator);
  173. $numerator = $numerator->plus($that->numerator->multipliedBy($this->denominator));
  174. $denominator = $this->denominator->multipliedBy($that->denominator);
  175. return new BigRational($numerator, $denominator, false);
  176. }
  177. /**
  178. * Returns the difference of this number and the given one.
  179. *
  180. * @param BigNumber|int|float|string $that The number to subtract.
  181. *
  182. * @throws MathException If the number is not valid.
  183. */
  184. public function minus(BigNumber|int|float|string $that) : BigRational
  185. {
  186. $that = BigRational::of($that);
  187. $numerator = $this->numerator->multipliedBy($that->denominator);
  188. $numerator = $numerator->minus($that->numerator->multipliedBy($this->denominator));
  189. $denominator = $this->denominator->multipliedBy($that->denominator);
  190. return new BigRational($numerator, $denominator, false);
  191. }
  192. /**
  193. * Returns the product of this number and the given one.
  194. *
  195. * @param BigNumber|int|float|string $that The multiplier.
  196. *
  197. * @throws MathException If the multiplier is not a valid number.
  198. */
  199. public function multipliedBy(BigNumber|int|float|string $that) : BigRational
  200. {
  201. $that = BigRational::of($that);
  202. $numerator = $this->numerator->multipliedBy($that->numerator);
  203. $denominator = $this->denominator->multipliedBy($that->denominator);
  204. return new BigRational($numerator, $denominator, false);
  205. }
  206. /**
  207. * Returns the result of the division of this number by the given one.
  208. *
  209. * @param BigNumber|int|float|string $that The divisor.
  210. *
  211. * @throws MathException If the divisor is not a valid number, or is zero.
  212. */
  213. public function dividedBy(BigNumber|int|float|string $that) : BigRational
  214. {
  215. $that = BigRational::of($that);
  216. $numerator = $this->numerator->multipliedBy($that->denominator);
  217. $denominator = $this->denominator->multipliedBy($that->numerator);
  218. return new BigRational($numerator, $denominator, true);
  219. }
  220. /**
  221. * Returns this number exponentiated to the given value.
  222. *
  223. * @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000.
  224. */
  225. public function power(int $exponent) : BigRational
  226. {
  227. if ($exponent === 0) {
  228. $one = BigInteger::one();
  229. return new BigRational($one, $one, false);
  230. }
  231. if ($exponent === 1) {
  232. return $this;
  233. }
  234. return new BigRational(
  235. $this->numerator->power($exponent),
  236. $this->denominator->power($exponent),
  237. false
  238. );
  239. }
  240. /**
  241. * Returns the reciprocal of this BigRational.
  242. *
  243. * The reciprocal has the numerator and denominator swapped.
  244. *
  245. * @throws DivisionByZeroException If the numerator is zero.
  246. */
  247. public function reciprocal() : BigRational
  248. {
  249. return new BigRational($this->denominator, $this->numerator, true);
  250. }
  251. /**
  252. * Returns the absolute value of this BigRational.
  253. */
  254. public function abs() : BigRational
  255. {
  256. return new BigRational($this->numerator->abs(), $this->denominator, false);
  257. }
  258. /**
  259. * Returns the negated value of this BigRational.
  260. */
  261. public function negated() : BigRational
  262. {
  263. return new BigRational($this->numerator->negated(), $this->denominator, false);
  264. }
  265. /**
  266. * Returns the simplified value of this BigRational.
  267. */
  268. public function simplified() : BigRational
  269. {
  270. $gcd = $this->numerator->gcd($this->denominator);
  271. $numerator = $this->numerator->quotient($gcd);
  272. $denominator = $this->denominator->quotient($gcd);
  273. return new BigRational($numerator, $denominator, false);
  274. }
  275. public function compareTo(BigNumber|int|float|string $that) : int
  276. {
  277. return $this->minus($that)->getSign();
  278. }
  279. public function getSign() : int
  280. {
  281. return $this->numerator->getSign();
  282. }
  283. public function toBigInteger() : BigInteger
  284. {
  285. $simplified = $this->simplified();
  286. if (! $simplified->denominator->isEqualTo(1)) {
  287. throw new RoundingNecessaryException('This rational number cannot be represented as an integer value without rounding.');
  288. }
  289. return $simplified->numerator;
  290. }
  291. public function toBigDecimal() : BigDecimal
  292. {
  293. return $this->numerator->toBigDecimal()->exactlyDividedBy($this->denominator);
  294. }
  295. public function toBigRational() : BigRational
  296. {
  297. return $this;
  298. }
  299. public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
  300. {
  301. return $this->numerator->toBigDecimal()->dividedBy($this->denominator, $scale, $roundingMode);
  302. }
  303. public function toInt() : int
  304. {
  305. return $this->toBigInteger()->toInt();
  306. }
  307. public function toFloat() : float
  308. {
  309. $simplified = $this->simplified();
  310. return $simplified->numerator->toFloat() / $simplified->denominator->toFloat();
  311. }
  312. public function __toString() : string
  313. {
  314. $numerator = (string) $this->numerator;
  315. $denominator = (string) $this->denominator;
  316. if ($denominator === '1') {
  317. return $numerator;
  318. }
  319. return $this->numerator . '/' . $this->denominator;
  320. }
  321. /**
  322. * This method is required for serializing the object and SHOULD NOT be accessed directly.
  323. *
  324. * @internal
  325. *
  326. * @return array{numerator: BigInteger, denominator: BigInteger}
  327. */
  328. public function __serialize(): array
  329. {
  330. return ['numerator' => $this->numerator, 'denominator' => $this->denominator];
  331. }
  332. /**
  333. * This method is only here to allow unserializing the object and cannot be accessed directly.
  334. *
  335. * @internal
  336. * @psalm-suppress RedundantPropertyInitializationCheck
  337. *
  338. * @param array{numerator: BigInteger, denominator: BigInteger} $data
  339. *
  340. * @throws \LogicException
  341. */
  342. public function __unserialize(array $data): void
  343. {
  344. if (isset($this->numerator)) {
  345. throw new \LogicException('__unserialize() is an internal function, it must not be called directly.');
  346. }
  347. $this->numerator = $data['numerator'];
  348. $this->denominator = $data['denominator'];
  349. }
  350. }