BigRational.php 12 KB

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