1use rust_decimal::Decimal;
13use rust_decimal::MathematicalOps;
14use std::fmt;
15use std::str::FromStr;
16use std::sync::LazyLock;
17
18use crate::computation::bigint::AllocError;
19pub(crate) use crate::computation::bigint::{BigInt, BigUint};
20use crate::limits::MAX_RATIONAL_BITS;
21
22const DECIMAL_OUTPUT_DIGIT_LIMIT: usize = Decimal::MAX_SCALE as usize;
26
27static DECIMAL_MAX_RATIONAL: LazyLock<RationalInteger> =
28 LazyLock::new(|| decimal_to_rational_unchecked(Decimal::MAX.normalize()));
29
30static DECIMAL_MIN_RATIONAL: LazyLock<RationalInteger> =
31 LazyLock::new(|| decimal_to_rational_unchecked(Decimal::MIN.normalize()));
32
33fn decimal_to_rational_unchecked(decimal: Decimal) -> RationalInteger {
34 decimal_to_rational(decimal).expect("BUG: Decimal::MAX/MIN must convert to rational")
35}
36
37fn gcd_u128(mut a: u128, mut b: u128) -> u128 {
42 if a == 0 {
43 return b;
44 }
45 if b == 0 {
46 return a;
47 }
48 let shift = (a | b).trailing_zeros();
49 a >>= a.trailing_zeros();
50 loop {
51 b >>= b.trailing_zeros();
52 if a > b {
53 std::mem::swap(&mut a, &mut b);
54 }
55 b -= a;
56 if b == 0 {
57 return a << shift;
58 }
59 }
60}
61
62fn reduce_small(numer: i128, denom: i128) -> (i128, i128) {
64 debug_assert!(denom > 0);
65 if numer == 0 {
66 return (0, 1);
67 }
68 let g = i128::try_from(gcd_u128(numer.unsigned_abs(), denom.unsigned_abs()))
69 .expect("BUG: gcd divides the positive i128 denominator");
70 (numer / g, denom / g)
71}
72
73#[derive(Clone, Debug)]
80enum Repr {
81 Small { numer: i128, denom: i128 },
82 Big { numer: BigInt, denom: BigInt },
83}
84
85#[derive(Clone, Debug)]
86pub struct RationalInteger {
87 repr: Repr,
88}
89
90impl RationalInteger {
91 fn to_big_numer(&self) -> BigInt {
92 match &self.repr {
93 Repr::Small { numer, .. } => BigInt::from_i128(*numer),
94 Repr::Big { numer, .. } => numer.clone(),
95 }
96 }
97
98 fn to_big_denom(&self) -> BigInt {
99 match &self.repr {
100 Repr::Small { denom, .. } => BigInt::from_i128(*denom),
101 Repr::Big { denom, .. } => denom.clone(),
102 }
103 }
104
105 fn as_big_pair(&self) -> (BigInt, BigInt) {
107 (self.to_big_numer(), self.to_big_denom())
108 }
109}
110
111impl RationalInteger {
112 fn from_reduced_small(numer: i128, denom: i128) -> Self {
116 debug_assert!(denom > 0);
117 Self {
118 repr: Repr::Small { numer, denom },
119 }
120 }
121
122 fn from_small(numer: i128, denom: i128) -> Result<Self, NumericFailure> {
124 if denom == 0 {
125 return Err(NumericFailure::DivisionByZero);
126 }
127 let (n, d) = if denom < 0 {
128 match (numer.checked_neg(), denom.checked_neg()) {
129 (Some(n), Some(d)) => (n, d),
130 _ => return Self::reduce_big(BigInt::from_i128(numer), BigInt::from_i128(denom)),
131 }
132 } else {
133 (numer, denom)
134 };
135 let (rn, rd) = reduce_small(n, d);
136 Ok(Self::from_reduced_small(rn, rd))
137 }
138
139 fn from_coprime(numer: BigInt, denom: BigInt) -> Self {
142 assert!(
143 denom.is_positive(),
144 "BUG: from_coprime requires a positive denominator, got {denom}"
145 );
146 match (numer.to_i128(), denom.to_i128()) {
147 (Some(n), Some(d)) => Self::from_reduced_small(n, d),
148 _ => Self {
149 repr: Repr::Big { numer, denom },
150 },
151 }
152 }
153
154 fn try_reciprocal_pair(&self) -> Result<(BigInt, BigInt), NumericFailure> {
156 if self.numer_is_zero() {
157 return Err(NumericFailure::DivisionByZero);
158 }
159 let (numer, denom) = self.as_big_pair();
160 if numer.is_negative() {
161 Ok((denom.try_neg()?, numer.try_neg()?))
162 } else {
163 Ok((denom, numer))
164 }
165 }
166
167 fn try_reciprocal(&self) -> Result<Self, NumericFailure> {
168 let (numer, denom) = self.try_reciprocal_pair()?;
169 Ok(Self::from_coprime(numer, denom))
170 }
171
172 pub fn try_new(numer: BigInt, denom: BigInt) -> Result<Self, NumericFailure> {
173 if denom.is_zero() {
174 return Err(NumericFailure::DivisionByZero);
175 }
176 if let (Some(n), Some(d)) = (numer.to_i128(), denom.to_i128()) {
178 return Self::from_small(n, d);
179 }
180 Self::reduce_big(numer, denom)
181 }
182
183 pub fn from_i64_pair(numer: i64, denom: i64) -> Self {
184 Self::from_small(i128::from(numer), i128::from(denom))
185 .expect("BUG: i64 rational reduce cannot fail")
186 }
187
188 pub fn from_bigint_strings(numer_str: &str, denom_str: &str) -> Result<Self, NumericFailure> {
190 let n = BigInt::try_from_str_radix(numer_str, 10).map_err(NumericFailure::from)?;
191 let d = BigInt::try_from_str_radix(denom_str, 10).map_err(NumericFailure::from)?;
192 Self::try_new(n, d)
193 }
194
195 pub fn is_integer(&self) -> bool {
198 match &self.repr {
199 Repr::Small { denom, .. } => *denom == 1,
200 Repr::Big { denom, .. } => *denom == BigInt::one(),
201 }
202 }
203
204 pub fn numer_is_zero(&self) -> bool {
205 match &self.repr {
206 Repr::Small { numer, .. } => *numer == 0,
207 Repr::Big { numer, .. } => numer.is_zero(),
208 }
209 }
210
211 pub fn numer_is_positive(&self) -> bool {
212 match &self.repr {
213 Repr::Small { numer, .. } => *numer > 0,
214 Repr::Big { numer, .. } => numer.is_positive(),
215 }
216 }
217
218 pub fn numer_is_negative(&self) -> bool {
219 match &self.repr {
220 Repr::Small { numer, .. } => *numer < 0,
221 Repr::Big { numer, .. } => numer.is_negative(),
222 }
223 }
224
225 pub fn numer_to_i32(&self) -> Option<i32> {
228 match &self.repr {
229 Repr::Small { numer, .. } => i32::try_from(*numer).ok(),
230 Repr::Big { numer, .. } => numer.to_i32(),
231 }
232 }
233
234 pub fn numer_to_i128(&self) -> Option<i128> {
235 match &self.repr {
236 Repr::Small { numer, .. } => Some(*numer),
237 Repr::Big { numer, .. } => numer.to_i128(),
238 }
239 }
240
241 pub fn numer_to_u8(&self) -> Option<u8> {
242 match &self.repr {
243 Repr::Small { numer, .. } => u8::try_from(*numer).ok(),
244 Repr::Big { numer, .. } => numer.to_i32().and_then(|v| u8::try_from(v).ok()),
245 }
246 }
247
248 pub fn numer_to_usize(&self) -> Option<usize> {
249 match &self.repr {
250 Repr::Small { numer, .. } => usize::try_from(*numer).ok(),
251 Repr::Big { numer, .. } => numer.to_usize(),
252 }
253 }
254
255 pub fn numer_to_string(&self) -> String {
256 match &self.repr {
257 Repr::Small { numer, .. } => numer.to_string(),
258 Repr::Big { numer, .. } => numer.to_string(),
259 }
260 }
261
262 pub fn denom_to_string(&self) -> String {
263 match &self.repr {
264 Repr::Small { denom, .. } => denom.to_string(),
265 Repr::Big { denom, .. } => denom.to_string(),
266 }
267 }
268
269 pub fn numer_magnitude_bits(&self) -> u64 {
271 match &self.repr {
272 Repr::Small { numer, .. } => {
273 if *numer == 0 {
274 0
275 } else {
276 128 - numer.unsigned_abs().leading_zeros() as u64
277 }
278 }
279 Repr::Big { numer, .. } => numer.magnitude().bits(),
280 }
281 }
282
283 pub fn denom_magnitude_bits(&self) -> u64 {
285 match &self.repr {
286 Repr::Small { denom, .. } => {
287 if *denom == 0 {
288 0
289 } else {
290 128 - denom.unsigned_abs().leading_zeros() as u64
291 }
292 }
293 Repr::Big { denom, .. } => denom.magnitude().bits(),
294 }
295 }
296
297 pub fn try_reduce_ref(rational: &RationalInteger) -> Result<Self, NumericFailure> {
300 match &rational.repr {
301 Repr::Small { numer, denom } => Self::from_small(*numer, *denom),
302 Repr::Big { numer, denom } => Self::reduce_big(numer.try_clone()?, denom.try_clone()?),
303 }
304 }
305
306 fn reduce_big(numer: BigInt, denom: BigInt) -> Result<Self, NumericFailure> {
308 if denom.is_zero() {
309 return Err(NumericFailure::DivisionByZero);
310 }
311 let (n, d) = if denom.is_negative() {
312 (numer.try_neg()?, denom.try_neg()?)
313 } else {
314 (numer, denom)
315 };
316 if n.is_zero() {
317 return Ok(Self::from_reduced_small(0, 1));
318 }
319 let gcd = n.try_abs()?.try_gcd(&d)?;
320 let n = n.try_div_trunc(&gcd)?;
321 let d = d.try_div_trunc(&gcd)?;
322 if let (Some(sn), Some(sd)) = (n.to_i128(), d.to_i128()) {
323 Ok(Self::from_reduced_small(sn, sd))
324 } else {
325 Ok(Self {
326 repr: Repr::Big { numer: n, denom: d },
327 })
328 }
329 }
330
331 pub fn try_reduce(self) -> Result<Self, NumericFailure> {
332 Self::try_reduce_ref(&self)
333 }
334
335 pub fn try_cmp(&self, other: &Self) -> Result<std::cmp::Ordering, NumericFailure> {
338 match (&self.repr, &other.repr) {
339 (
340 Repr::Small {
341 numer: ln,
342 denom: ld,
343 },
344 Repr::Small {
345 numer: rn,
346 denom: rd,
347 },
348 ) => {
349 if let (Some(left), Some(right)) = (ln.checked_mul(*rd), rn.checked_mul(*ld)) {
352 return Ok(left.cmp(&right));
353 }
354 let left = BigInt::from_i128(*ln).try_mul(&BigInt::from_i128(*rd))?;
356 let right = BigInt::from_i128(*rn).try_mul(&BigInt::from_i128(*ld))?;
357 Ok(left.cmp(&right))
358 }
359 _ => {
360 let left = self.to_big_numer().try_mul(&other.to_big_denom())?;
361 let right = other.to_big_numer().try_mul(&self.to_big_denom())?;
362 Ok(left.cmp(&right))
363 }
364 }
365 }
366
367 pub fn try_to_decimal(&self) -> Result<Decimal, NumericFailure> {
376 if self.numer_is_zero() {
377 return Ok(Decimal::ZERO);
378 }
379
380 if self.try_cmp(&DECIMAL_MAX_RATIONAL)? == std::cmp::Ordering::Greater {
381 return Err(NumericFailure::Overflow);
382 }
383 if self.try_cmp(&DECIMAL_MIN_RATIONAL)? == std::cmp::Ordering::Less {
384 return Err(NumericFailure::Overflow);
385 }
386
387 if let Repr::Small { numer, denom: 1 } = &self.repr {
389 return Decimal::from_str(&numer.to_string()).map_err(|_| NumericFailure::Overflow);
390 }
391
392 let negative = self.numer_is_negative();
393 let abs_numer = self.to_big_numer().try_abs()?;
394 let abs_denom = self.to_big_denom().try_abs()?;
395
396 let (int_quotient, mut remainder) = abs_numer.try_div_rem(&abs_denom)?;
397
398 let mut digit_string = if int_quotient.is_zero() {
399 String::from("0")
400 } else {
401 int_quotient.to_string()
402 };
403
404 if !remainder.is_zero() {
405 digit_string.push('.');
406 let integer_significant_digits = if int_quotient.is_zero() {
407 0
408 } else {
409 digit_string.len()
410 };
411
412 if int_quotient.is_zero() {
413 let mut significant_digits = 0usize;
414 while !remainder.is_zero() && significant_digits < DECIMAL_OUTPUT_DIGIT_LIMIT + 1 {
415 remainder = remainder.try_mul(&BigInt::from_i64(10))?;
416 let (digit_quotient, new_remainder) = remainder.try_div_rem(&abs_denom)?;
417 let digit = u8::try_from(
418 digit_quotient
419 .to_i32()
420 .expect("BUG: fractional digit quotient must fit i32"),
421 )
422 .expect("BUG: fractional digit must be 0-9");
423 digit_string.push(char::from(b'0' + digit));
424 remainder = new_remainder;
425 if digit != 0 || significant_digits > 0 {
426 significant_digits += 1;
427 }
428 }
429 } else {
430 let max_fractional_digits = DECIMAL_OUTPUT_DIGIT_LIMIT
431 .saturating_sub(integer_significant_digits)
432 .saturating_add(1);
433 let mut fractional_count = 0usize;
434 while !remainder.is_zero() && fractional_count < max_fractional_digits {
435 remainder = remainder.try_mul(&BigInt::from_i64(10))?;
436 let (digit_quotient, new_remainder) = remainder.try_div_rem(&abs_denom)?;
437 let digit = u8::try_from(
438 digit_quotient
439 .to_i32()
440 .expect("BUG: fractional digit quotient must fit i32"),
441 )
442 .expect("BUG: fractional digit must be 0-9");
443 digit_string.push(char::from(b'0' + digit));
444 remainder = new_remainder;
445 fractional_count += 1;
446 }
447 }
448 }
449
450 if negative {
451 digit_string.insert(0, '-');
452 }
453
454 Decimal::from_str(&digit_string).map_err(|_| NumericFailure::Overflow)
455 }
456
457 pub fn try_to_decimal_string(&self) -> Result<String, NumericFailure> {
458 self.try_to_decimal()
459 .map(|decimal| decimal_to_display_str(&decimal))
460 }
461
462 pub fn display_str(&self) -> String {
464 match self.try_to_decimal() {
465 Ok(decimal) => decimal_to_display_str(&decimal),
466 Err(NumericFailure::Overflow) => rational_fraction_str(self),
467 Err(NumericFailure::OutOfMemory) => rational_fraction_str(self),
468 Err(NumericFailure::DivisionByZero) => {
469 unreachable!("BUG: display_str on reduced rational with zero denominator")
470 }
471 Err(NumericFailure::Irrational) => {
472 unreachable!("BUG: display_str does not perform irrational operations")
473 }
474 Err(NumericFailure::MagnitudeLimit) => {
475 unreachable!("BUG: try_to_decimal does not apply the arithmetic magnitude limit")
476 }
477 }
478 }
479}
480
481impl PartialEq for RationalInteger {
484 fn eq(&self, other: &Self) -> bool {
485 match (&self.repr, &other.repr) {
486 (
487 Repr::Small {
488 numer: ln,
489 denom: ld,
490 },
491 Repr::Small {
492 numer: rn,
493 denom: rd,
494 },
495 ) => ln == rn && ld == rd,
496 _ => {
497 self.to_big_numer() == other.to_big_numer()
498 && self.to_big_denom() == other.to_big_denom()
499 }
500 }
501 }
502}
503
504impl Eq for RationalInteger {}
505
506impl std::hash::Hash for RationalInteger {
507 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
508 match &self.repr {
509 Repr::Small { numer, denom } => {
510 numer.hash(state);
511 denom.hash(state);
512 }
513 Repr::Big { numer, denom } => {
514 if let (Some(n), Some(d)) = (numer.to_i128(), denom.to_i128()) {
516 n.hash(state);
517 d.hash(state);
518 } else {
519 numer.hash(state);
520 denom.hash(state);
521 }
522 }
523 }
524 }
525}
526
527impl fmt::Display for RationalInteger {
528 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
529 write!(f, "{}", self.display_str())
530 }
531}
532
533#[derive(Debug, Clone, PartialEq, Eq)]
538pub enum NumericFailure {
539 DivisionByZero,
540 Overflow,
541 OutOfMemory,
542 MagnitudeLimit,
544 Irrational,
545}
546
547impl fmt::Display for NumericFailure {
548 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
549 match self {
550 NumericFailure::DivisionByZero => {
551 formatter.write_str("Division by zero is not allowed.")
552 }
553 NumericFailure::Overflow => formatter.write_str("The numeric result overflowed."),
554 NumericFailure::OutOfMemory => {
555 formatter.write_str("The calculation ran out of memory.")
556 }
557 NumericFailure::MagnitudeLimit => write!(
558 formatter,
559 "The numeric result exceeds the maximum size of {MAX_RATIONAL_BITS} bits."
560 ),
561 NumericFailure::Irrational => formatter
562 .write_str("The numeric result is irrational and cannot be represented exactly."),
563 }
564 }
565}
566
567impl From<AllocError> for NumericFailure {
568 fn from(_: AllocError) -> Self {
569 NumericFailure::OutOfMemory
570 }
571}
572
573#[derive(Debug, Clone, Copy, PartialEq, Eq)]
574pub enum NumericOperation {
575 Add,
576 Subtract,
577 Multiply,
578 Divide,
579 Modulo,
580 Power,
581}
582
583pub fn rational_one() -> RationalInteger {
588 rational_new(1, 1)
589}
590
591pub fn rational_zero() -> RationalInteger {
592 rational_new(0, 1)
593}
594
595pub fn rational_new(numer: i64, denom: i64) -> RationalInteger {
596 RationalInteger::from_i64_pair(numer, denom)
597}
598
599pub fn try_rational_new(numer: BigInt, denom: BigInt) -> Result<RationalInteger, NumericFailure> {
600 RationalInteger::try_new(numer, denom)
601}
602
603pub fn rational_is_zero(rational: &RationalInteger) -> bool {
604 rational.numer_is_zero()
605}
606
607pub fn rational_abs(rational: &RationalInteger) -> Result<RationalInteger, NumericFailure> {
608 match &rational.repr {
609 Repr::Small { numer, denom } => match numer.checked_abs() {
610 Some(n) => Ok(RationalInteger::from_reduced_small(n, *denom)),
611 None => try_rational_new(
612 BigInt::from_i128(*numer).try_abs()?,
613 BigInt::from_i128(*denom),
614 ),
615 },
616 Repr::Big { numer, denom } => {
617 let n = numer.try_abs()?;
618 let d = denom.try_clone()?;
619 Ok(RationalInteger {
620 repr: Repr::Big { numer: n, denom: d },
621 })
622 }
623 }
624}
625
626pub fn rational_trunc(rational: &RationalInteger) -> Result<RationalInteger, NumericFailure> {
627 match &rational.repr {
628 Repr::Small { numer, denom } => Ok(RationalInteger::from_reduced_small(numer / denom, 1)),
629 Repr::Big { numer, denom } => {
630 let truncated = numer.try_div_trunc(denom)?;
631 try_rational_new(truncated, BigInt::one())
632 }
633 }
634}
635
636pub fn rational_floor(rational: &RationalInteger) -> Result<RationalInteger, NumericFailure> {
637 let truncated = rational_trunc(rational)?;
638 if rational.numer_is_negative() && !rational.is_integer() {
639 try_sub(&truncated, &rational_one())
640 } else {
641 Ok(truncated)
642 }
643}
644
645pub fn rational_ceil(rational: &RationalInteger) -> Result<RationalInteger, NumericFailure> {
646 let truncated = rational_trunc(rational)?;
647 if !rational.numer_is_negative() && !rational.is_integer() {
648 try_add(&truncated, &rational_one())
649 } else {
650 Ok(truncated)
651 }
652}
653
654pub fn rational_round(rational: &RationalInteger) -> Result<RationalInteger, NumericFailure> {
656 let floor = rational_floor(rational)?;
657 let fraction = try_sub(rational, &floor)?;
658 match fraction.try_cmp(&rational_new(1, 2))? {
659 std::cmp::Ordering::Less => Ok(floor),
660 std::cmp::Ordering::Greater => try_add(&floor, &rational_one()),
661 std::cmp::Ordering::Equal => {
662 if rational.numer_is_negative() {
663 Ok(floor)
664 } else {
665 try_add(&floor, &rational_one())
666 }
667 }
668 }
669}
670
671pub fn decimal_to_rational(decimal: Decimal) -> Result<RationalInteger, NumericFailure> {
672 let mantissa = decimal.mantissa();
673 if mantissa == 0 {
674 return Ok(rational_new(0, 1));
675 }
676 let scale = decimal.scale();
677 if scale == 0 {
678 return RationalInteger::from_small(mantissa, 1);
679 }
680 if scale <= 38 {
682 if let Some(denom) = 10i128.checked_pow(scale) {
683 return RationalInteger::from_small(mantissa, denom);
684 }
685 }
686 let mut denominator = BigInt::one();
688 for _ in 0..scale {
689 denominator = denominator
690 .try_mul(&BigInt::from_i64(10))
691 .map_err(NumericFailure::from)?;
692 }
693 try_rational_new(BigInt::from_i128(mantissa), denominator)
694}
695
696pub(crate) fn decimal_to_display_str(decimal: &Decimal) -> String {
697 let normalized = decimal.normalize();
698 if normalized.fract().is_zero() {
699 normalized.trunc().to_string()
700 } else {
701 normalized.to_string()
702 }
703}
704
705fn rational_fraction_str(rational: &RationalInteger) -> String {
706 if rational.is_integer() {
707 rational.numer_to_string()
708 } else {
709 format!(
710 "{}/{}",
711 rational.numer_to_string(),
712 rational.denom_to_string()
713 )
714 }
715}
716
717pub fn rational_operation(
725 left: &RationalInteger,
726 operation: NumericOperation,
727 right: &RationalInteger,
728) -> Result<RationalInteger, NumericFailure> {
729 let result = rational_operation_unbounded(left, operation, right)?;
730 if result.numer_magnitude_bits() > MAX_RATIONAL_BITS
731 || result.denom_magnitude_bits() > MAX_RATIONAL_BITS
732 {
733 return Err(NumericFailure::MagnitudeLimit);
734 }
735 Ok(result)
736}
737
738fn rational_operation_unbounded(
739 left: &RationalInteger,
740 operation: NumericOperation,
741 right: &RationalInteger,
742) -> Result<RationalInteger, NumericFailure> {
743 match operation {
744 NumericOperation::Add => try_add(left, right),
745 NumericOperation::Subtract => try_sub(left, right),
746 NumericOperation::Multiply => try_mul(left, right),
747 NumericOperation::Divide => {
748 if rational_is_zero(right) {
749 return Err(NumericFailure::DivisionByZero);
750 }
751 try_div(left, right)
752 }
753 NumericOperation::Modulo => {
754 if rational_is_zero(right) {
755 return Err(NumericFailure::DivisionByZero);
756 }
757 let quotient = try_div(left, right)?;
758 let floored = rational_floor("ient)?;
759 let product = try_mul(&floored, right)?;
760 try_sub(left, &product)
761 }
762 NumericOperation::Power => try_rational_power(left, right),
763 }
764}
765
766pub fn rational_operation_with_fallback(
767 left: &RationalInteger,
768 operation: NumericOperation,
769 right: &RationalInteger,
770) -> Result<RationalInteger, NumericFailure> {
771 match rational_operation(left, operation, right) {
772 Ok(result) => Ok(result),
773 Err(NumericFailure::DivisionByZero) => Err(NumericFailure::DivisionByZero),
774 Err(NumericFailure::Overflow) => Err(NumericFailure::Overflow),
775 Err(NumericFailure::OutOfMemory) => Err(NumericFailure::OutOfMemory),
776 Err(NumericFailure::MagnitudeLimit) => Err(NumericFailure::MagnitudeLimit),
777 Err(NumericFailure::Irrational) => approximate_rational_operation(left, operation, right),
778 }
779}
780
781fn approximate_rational_operation(
782 left: &RationalInteger,
783 operation: NumericOperation,
784 right: &RationalInteger,
785) -> Result<RationalInteger, NumericFailure> {
786 let left_decimal = left.try_to_decimal()?;
787 let right_decimal = right.try_to_decimal()?;
788 let result_decimal = decimal_arithmetic(left_decimal, operation, right_decimal)?;
789 decimal_to_rational(result_decimal)
790}
791
792fn decimal_arithmetic(
793 left: Decimal,
794 operation: NumericOperation,
795 right: Decimal,
796) -> Result<Decimal, NumericFailure> {
797 match operation {
798 NumericOperation::Add => left.checked_add(right).ok_or(NumericFailure::Overflow),
799 NumericOperation::Subtract => left.checked_sub(right).ok_or(NumericFailure::Overflow),
800 NumericOperation::Multiply => left.checked_mul(right).ok_or(NumericFailure::Overflow),
801 NumericOperation::Divide => {
802 if right.is_zero() {
803 return Err(NumericFailure::DivisionByZero);
804 }
805 left.checked_div(right).ok_or(NumericFailure::Overflow)
806 }
807 NumericOperation::Modulo => {
808 if right.is_zero() {
809 return Err(NumericFailure::DivisionByZero);
810 }
811 let quotient = left.checked_div(right).ok_or(NumericFailure::Overflow)?;
812 let floored = quotient.floor();
813 let product = floored.checked_mul(right).ok_or(NumericFailure::Overflow)?;
814 left.checked_sub(product).ok_or(NumericFailure::Overflow)
815 }
816 NumericOperation::Power => decimal_power(left, right),
817 }
818}
819
820fn decimal_is_half(exponent: Decimal) -> bool {
821 exponent
822 .checked_mul(Decimal::TWO)
823 .is_some_and(|doubled| doubled == Decimal::ONE)
824}
825
826fn decimal_power(base: Decimal, exponent: Decimal) -> Result<Decimal, NumericFailure> {
827 if exponent.fract().is_zero() {
828 let exponent_i64 =
829 i64::try_from(exponent.trunc().mantissa()).map_err(|_| NumericFailure::Overflow)?;
830 return base
831 .checked_powi(exponent_i64)
832 .ok_or(NumericFailure::Overflow);
833 }
834 if decimal_is_half(exponent) {
835 return base.sqrt().ok_or(NumericFailure::Irrational);
836 }
837 Err(NumericFailure::Irrational)
838}
839
840pub fn try_add(
843 left: &RationalInteger,
844 right: &RationalInteger,
845) -> Result<RationalInteger, NumericFailure> {
846 if let (
847 Repr::Small {
848 numer: ln,
849 denom: ld,
850 },
851 Repr::Small {
852 numer: rn,
853 denom: rd,
854 },
855 ) = (&left.repr, &right.repr)
856 {
857 if let (Some(a), Some(b), Some(d)) = (
859 ln.checked_mul(*rd),
860 rn.checked_mul(*ld),
861 ld.checked_mul(*rd),
862 ) {
863 if let Some(n) = a.checked_add(b) {
864 return RationalInteger::from_small(n, d);
865 }
866 }
867 }
868 let (ln, ld) = left.as_big_pair();
870 let (rn, rd) = right.as_big_pair();
871 let numerator = ln.try_mul(&rd)?.try_add(&rn.try_mul(&ld)?)?;
872 let denominator = ld.try_mul(&rd)?;
873 try_rational_new(numerator, denominator)
874}
875
876pub fn try_sub(
877 left: &RationalInteger,
878 right: &RationalInteger,
879) -> Result<RationalInteger, NumericFailure> {
880 if let (
881 Repr::Small {
882 numer: ln,
883 denom: ld,
884 },
885 Repr::Small {
886 numer: rn,
887 denom: rd,
888 },
889 ) = (&left.repr, &right.repr)
890 {
891 if let (Some(a), Some(b), Some(d)) = (
892 ln.checked_mul(*rd),
893 rn.checked_mul(*ld),
894 ld.checked_mul(*rd),
895 ) {
896 if let Some(n) = a.checked_sub(b) {
897 return RationalInteger::from_small(n, d);
898 }
899 }
900 }
901 let (ln, ld) = left.as_big_pair();
902 let (rn, rd) = right.as_big_pair();
903 let numerator = ln.try_mul(&rd)?.try_sub(&rn.try_mul(&ld)?)?;
904 let denominator = ld.try_mul(&rd)?;
905 try_rational_new(numerator, denominator)
906}
907
908pub fn try_mul(
909 left: &RationalInteger,
910 right: &RationalInteger,
911) -> Result<RationalInteger, NumericFailure> {
912 if let (
913 Repr::Small {
914 numer: ln,
915 denom: ld,
916 },
917 Repr::Small {
918 numer: rn,
919 denom: rd,
920 },
921 ) = (&left.repr, &right.repr)
922 {
923 if let (Some(n), Some(d)) = (ln.checked_mul(*rn), ld.checked_mul(*rd)) {
924 return RationalInteger::from_small(n, d);
925 }
926 }
927 if left.numer_is_zero() || right.numer_is_zero() {
928 return Ok(rational_zero());
929 }
930 let (ln, ld) = left.as_big_pair();
931 let (rn, rd) = right.as_big_pair();
932 cross_cancelled_product((&ln, &ld), (&rn, &rd))
933}
934
935fn cross_cancelled_product(
939 (ln, ld): (&BigInt, &BigInt),
940 (rn, rd): (&BigInt, &BigInt),
941) -> Result<RationalInteger, NumericFailure> {
942 let left_cross = ln.try_gcd(rd)?;
943 let right_cross = rn.try_gcd(ld)?;
944 let numerator = ln
945 .try_div_trunc(&left_cross)?
946 .try_mul(&rn.try_div_trunc(&right_cross)?)?;
947 let denominator = ld
948 .try_div_trunc(&right_cross)?
949 .try_mul(&rd.try_div_trunc(&left_cross)?)?;
950 Ok(RationalInteger::from_coprime(numerator, denominator))
951}
952
953pub fn try_div(
954 left: &RationalInteger,
955 right: &RationalInteger,
956) -> Result<RationalInteger, NumericFailure> {
957 if right.numer_is_zero() {
958 return Err(NumericFailure::DivisionByZero);
959 }
960 if let (
961 Repr::Small {
962 numer: ln,
963 denom: ld,
964 },
965 Repr::Small {
966 numer: rn,
967 denom: rd,
968 },
969 ) = (&left.repr, &right.repr)
970 {
971 if let (Some(n), Some(d)) = (ln.checked_mul(*rd), ld.checked_mul(*rn)) {
972 return RationalInteger::from_small(n, d);
973 }
974 }
975 if left.numer_is_zero() {
976 return Ok(rational_zero());
977 }
978 let (ln, ld) = left.as_big_pair();
979 let (rn, rd) = right.try_reciprocal_pair()?;
980 cross_cancelled_product((&ln, &ld), (&rn, &rd))
981}
982
983pub fn try_pow_integer(
985 base: &RationalInteger,
986 exponent: &BigInt,
987) -> Result<RationalInteger, NumericFailure> {
988 if exponent.is_zero() {
989 return Ok(rational_one());
990 }
991 if exponent.is_negative() {
992 try_pow_magnitude(&base.try_reciprocal()?, exponent.magnitude())
993 } else {
994 try_pow_magnitude(base, exponent.magnitude())
995 }
996}
997
998fn try_pow_magnitude(
1001 base: &RationalInteger,
1002 exponent: &BigUint,
1003) -> Result<RationalInteger, NumericFailure> {
1004 if let (Repr::Small { numer, denom }, Some(small_exponent)) = (&base.repr, exponent.to_u32()) {
1005 if let (Some(n), Some(d)) = (
1006 numer.checked_pow(small_exponent),
1007 denom.checked_pow(small_exponent),
1008 ) {
1009 return Ok(RationalInteger::from_reduced_small(n, d));
1010 }
1011 }
1012 let (numer, denom) = base.as_big_pair();
1013 Ok(RationalInteger::from_coprime(
1014 numer.try_pow(exponent)?,
1015 denom.try_pow(exponent)?,
1016 ))
1017}
1018
1019pub fn try_rational_power(
1020 base: &RationalInteger,
1021 exponent: &RationalInteger,
1022) -> Result<RationalInteger, NumericFailure> {
1023 if !exponent.numer_is_positive() && !exponent.numer_is_negative() && exponent.numer_is_zero() {
1024 return Ok(rational_one());
1025 }
1026
1027 if exponent.is_integer() {
1028 let integer_exponent = exponent.to_big_numer();
1029 if power_exceeds_magnitude_limit(base, integer_exponent.magnitude()) {
1030 return Err(NumericFailure::MagnitudeLimit);
1031 }
1032 return try_pow_integer(base, &integer_exponent);
1033 }
1034
1035 if base.numer_is_zero() {
1036 if exponent.numer_is_negative() || exponent.numer_is_zero() {
1037 return Err(NumericFailure::DivisionByZero);
1038 }
1039 return Ok(rational_new(0, 1));
1040 }
1041
1042 let root_degree = exponent.to_big_denom().magnitude().clone();
1043 let abs_exponent = exponent.to_big_numer();
1044
1045 let result_negative = base.numer_is_negative() && abs_exponent.magnitude().is_odd();
1046 if result_negative && !root_degree.is_odd() {
1047 return Err(NumericFailure::Irrational);
1048 }
1049
1050 let numer_root = exact_integer_root(&base.to_big_numer().try_abs()?, &root_degree)?;
1051 let denom_root = exact_integer_root(&base.to_big_denom(), &root_degree)?;
1052 let signed_numer_root = if result_negative {
1053 numer_root.try_neg()?
1054 } else {
1055 numer_root
1056 };
1057 let root = RationalInteger::from_coprime(signed_numer_root, denom_root);
1058 if power_exceeds_magnitude_limit(&root, abs_exponent.magnitude()) {
1059 return Err(NumericFailure::MagnitudeLimit);
1060 }
1061 let raised = try_pow_magnitude(&root, abs_exponent.magnitude())?;
1062
1063 if exponent.numer_is_negative() {
1064 raised.try_reciprocal()
1065 } else {
1066 Ok(raised)
1067 }
1068}
1069
1070fn power_exceeds_magnitude_limit(base: &RationalInteger, exponent: &BigUint) -> bool {
1075 [base.numer_magnitude_bits(), base.denom_magnitude_bits()]
1076 .into_iter()
1077 .filter(|bits| *bits >= 2)
1078 .any(|bits| match exponent.to_u32() {
1079 Some(exponent) => (bits - 1)
1080 .checked_mul(u64::from(exponent))
1081 .is_none_or(|lower_bound| lower_bound >= MAX_RATIONAL_BITS),
1082 None => true,
1083 })
1084}
1085
1086fn exact_integer_root(value: &BigInt, degree: &BigUint) -> Result<BigInt, NumericFailure> {
1088 let root = value.try_nth_root(degree)?;
1089 if root.try_pow(degree)? == *value {
1090 Ok(root)
1091 } else {
1092 Err(NumericFailure::Irrational)
1093 }
1094}
1095
1096pub use {try_div as checked_div, try_mul as checked_mul, try_pow_integer as checked_pow_integer};
1097
1098#[cfg(test)]
1103mod tests {
1104 use super::*;
1105 use rust_decimal::Decimal;
1106 use std::str::FromStr;
1107
1108 #[test]
1109 fn power_of_two_with_all_ones_exponent_is_positive() {
1110 let two_pow_127 =
1111 RationalInteger::from_bigint_strings("170141183460469231731687303715884105728", "1")
1112 .unwrap();
1113 assert!(two_pow_127.numer_is_positive(), "parsed 2^127 sign");
1114 let big =
1115 BigInt::try_from_str_radix("170141183460469231731687303715884105728", 10).unwrap();
1116 assert!(big.is_positive(), "BigInt 2^127 sign");
1117 let product = try_mul(&rational_new(1 << 62, 1), &rational_new(1 << 62, 1)).unwrap();
1118 let product = try_mul(&product, &rational_new(8, 1)).unwrap();
1119 assert!(product.numer_is_positive(), "2^62 * 2^62 * 8 sign");
1120 let powered = try_pow_integer(&rational_new(2, 1), &BigInt::from_i64(127)).unwrap();
1121 assert!(powered.numer_is_positive(), "2^127 via power sign");
1122 assert_eq!(powered, two_pow_127);
1123 }
1124
1125 #[test]
1126 fn i128_min_numerator_promotes_instead_of_failing() {
1127 let two_pow_127 =
1128 RationalInteger::from_bigint_strings("170141183460469231731687303715884105728", "1")
1129 .unwrap();
1130 let min = RationalInteger::from_small(i128::MIN, 1).unwrap();
1131 assert_eq!(rational_abs(&min).unwrap(), two_pow_127);
1132 assert_eq!(try_div(&min, &rational_new(-1, 1)).unwrap(), two_pow_127);
1133 let reciprocal = try_div(&rational_new(1, 1), &min).unwrap();
1134 assert_eq!(
1135 reciprocal,
1136 RationalInteger::from_bigint_strings("-1", "170141183460469231731687303715884105728")
1137 .unwrap()
1138 );
1139 assert_eq!(
1140 RationalInteger::from_small(i128::MIN, -2).unwrap(),
1141 RationalInteger::from_bigint_strings("85070591730234615865843651857942052864", "1")
1142 .unwrap()
1143 );
1144 assert_eq!(
1145 RationalInteger::from_small(1, i128::MIN).unwrap(),
1146 RationalInteger::from_bigint_strings("-1", "170141183460469231731687303715884105728")
1147 .unwrap()
1148 );
1149 assert_eq!(
1150 RationalInteger::from_small(i128::MIN, i128::MIN).unwrap(),
1151 rational_one()
1152 );
1153 assert_eq!(
1154 RationalInteger::from_small(3, i128::MIN).unwrap(),
1155 RationalInteger::from_bigint_strings("-3", "170141183460469231731687303715884105728")
1156 .unwrap()
1157 );
1158 }
1159
1160 fn two_pow(exponent: i64) -> RationalInteger {
1161 try_pow_integer(&rational_new(2, 1), &BigInt::from_i64(exponent)).unwrap()
1162 }
1163
1164 fn operate(
1165 left: &RationalInteger,
1166 operation: NumericOperation,
1167 right: &RationalInteger,
1168 ) -> Result<RationalInteger, NumericFailure> {
1169 rational_operation(left, operation, right)
1170 }
1171
1172 fn negate(value: &RationalInteger) -> RationalInteger {
1173 try_sub(&rational_zero(), value).unwrap()
1174 }
1175
1176 const LIMIT: i64 = MAX_RATIONAL_BITS as i64;
1177
1178 #[test]
1179 fn rational_operation_accepts_results_at_the_bit_limit() {
1180 let at_limit = two_pow(LIMIT - 1);
1181 assert_eq!(at_limit.numer_magnitude_bits(), MAX_RATIONAL_BITS);
1182 let half_limit = two_pow(LIMIT / 2 - 1);
1183 assert_eq!(
1184 operate(&half_limit, NumericOperation::Multiply, &two_pow(LIMIT / 2)).unwrap(),
1185 at_limit
1186 );
1187 assert_eq!(
1188 operate(
1189 &two_pow(LIMIT - 2),
1190 NumericOperation::Add,
1191 &two_pow(LIMIT - 2)
1192 )
1193 .unwrap(),
1194 at_limit
1195 );
1196 let reciprocal = operate(&rational_one(), NumericOperation::Divide, &at_limit).unwrap();
1197 assert_eq!(reciprocal.denom_magnitude_bits(), MAX_RATIONAL_BITS);
1198 }
1199
1200 #[test]
1201 fn rational_operation_rejects_numerator_beyond_the_bit_limit() {
1202 let at_limit = two_pow(LIMIT - 1);
1203 let two = rational_new(2, 1);
1204 for (operation, right) in [
1205 (NumericOperation::Add, at_limit.clone()),
1206 (NumericOperation::Subtract, negate(&at_limit)),
1207 (NumericOperation::Multiply, two.clone()),
1208 (NumericOperation::Divide, rational_new(1, 2)),
1209 (NumericOperation::Power, two.clone()),
1210 ] {
1211 assert_eq!(
1212 operate(&at_limit, operation, &right).unwrap_err(),
1213 NumericFailure::MagnitudeLimit,
1214 "{operation:?}"
1215 );
1216 }
1217 }
1218
1219 #[test]
1220 fn rational_operation_rejects_denominator_beyond_the_bit_limit() {
1221 let reciprocal_at_limit = try_div(&rational_one(), &two_pow(LIMIT - 1)).unwrap();
1222 assert_eq!(
1223 operate(
1224 &reciprocal_at_limit,
1225 NumericOperation::Divide,
1226 &rational_new(2, 1)
1227 )
1228 .unwrap_err(),
1229 NumericFailure::MagnitudeLimit
1230 );
1231 assert_eq!(
1232 operate(
1233 &reciprocal_at_limit,
1234 NumericOperation::Multiply,
1235 &rational_new(1, 2)
1236 )
1237 .unwrap_err(),
1238 NumericFailure::MagnitudeLimit
1239 );
1240 }
1241
1242 #[test]
1243 fn power_precheck_is_exact_at_the_bit_limit() {
1244 let two = rational_new(2, 1);
1245 assert_eq!(
1246 operate(&two, NumericOperation::Power, &rational_new(LIMIT - 1, 1)).unwrap(),
1247 two_pow(LIMIT - 1)
1248 );
1249 assert!(!power_exceeds_magnitude_limit(
1250 &two,
1251 &BigUint::try_from_u32(LIMIT as u32 - 1).unwrap()
1252 ));
1253 assert!(power_exceeds_magnitude_limit(
1254 &two,
1255 &BigUint::try_from_u32(LIMIT as u32).unwrap()
1256 ));
1257 let two_pow_sixteen = rational_new(1 << 16, 1);
1258 let largest_fitting_exponent = (LIMIT as u32 - 1) / 16;
1259 assert!(!power_exceeds_magnitude_limit(
1260 &two_pow_sixteen,
1261 &BigUint::try_from_u32(largest_fitting_exponent).unwrap()
1262 ));
1263 assert_eq!(
1264 operate(
1265 &two_pow_sixteen,
1266 NumericOperation::Power,
1267 &rational_new(i64::from(largest_fitting_exponent), 1)
1268 )
1269 .unwrap(),
1270 two_pow(16 * i64::from(largest_fitting_exponent))
1271 );
1272 }
1273
1274 #[test]
1275 fn power_precheck_rejects_huge_exponents_before_allocating() {
1276 let huge = RationalInteger::from_bigint_strings("1180591620717411303424", "1").unwrap();
1277 for base in [rational_new(2, 1), rational_new(1, 2), rational_new(-3, 7)] {
1278 assert_eq!(
1279 operate(&base, NumericOperation::Power, &huge).unwrap_err(),
1280 NumericFailure::MagnitudeLimit,
1281 "{base:?}"
1282 );
1283 assert_eq!(
1284 operate(&base, NumericOperation::Power, &negate(&huge)).unwrap_err(),
1285 NumericFailure::MagnitudeLimit,
1286 "{base:?} negative exponent"
1287 );
1288 }
1289 assert_eq!(
1290 operate(&rational_new(-1, 1), NumericOperation::Power, &huge).unwrap(),
1291 rational_one()
1292 );
1293 }
1294
1295 #[test]
1296 fn power_precheck_applies_to_the_root_of_fractional_exponents() {
1297 let four = rational_new(4, 1);
1298 assert_eq!(
1299 operate(&four, NumericOperation::Power, &rational_new(LIMIT - 1, 2)).unwrap(),
1300 two_pow(LIMIT - 1)
1301 );
1302 assert_eq!(
1303 operate(&four, NumericOperation::Power, &rational_new(LIMIT + 1, 2)).unwrap_err(),
1304 NumericFailure::MagnitudeLimit
1305 );
1306 }
1307
1308 use crate::computation::bigint::work_counters;
1309
1310 fn big(value: i64) -> BigInt {
1311 BigInt::from_i64(value)
1312 }
1313
1314 fn big_pow(base: i64, exponent: u32) -> BigInt {
1315 big(base)
1316 .try_pow(&BigUint::try_from_u32(exponent).unwrap())
1317 .unwrap()
1318 }
1319
1320 fn big_add(left: &BigInt, right: &BigInt) -> BigInt {
1321 left.try_add(right).unwrap()
1322 }
1323
1324 fn fraction(numer: BigInt, denom: BigInt) -> RationalInteger {
1325 try_rational_new(numer, denom).unwrap()
1326 }
1327
1328 fn assert_canonical(value: &RationalInteger) {
1329 match &value.repr {
1330 Repr::Small { numer, denom } => {
1331 assert!(*denom > 0, "{value:?} denominator must be positive");
1332 assert_eq!(
1333 reduce_small(*numer, *denom),
1334 (*numer, *denom),
1335 "{value:?} is not reduced"
1336 );
1337 }
1338 Repr::Big { numer, denom } => {
1339 assert!(
1340 denom.is_positive(),
1341 "{value:?} denominator must be positive"
1342 );
1343 assert!(
1344 numer.to_i128().is_none() || denom.to_i128().is_none(),
1345 "{value:?} fits i128 and must be Small"
1346 );
1347 let reduced = RationalInteger::try_reduce_ref(value).expect("reducible");
1348 assert!(&reduced == value, "{value:?} is not reduced");
1349 }
1350 }
1351 }
1352
1353 fn reference_mul(left: &RationalInteger, right: &RationalInteger) -> RationalInteger {
1354 let (ln, ld) = left.as_big_pair();
1355 let (rn, rd) = right.as_big_pair();
1356 fraction(ln.try_mul(&rn).unwrap(), ld.try_mul(&rd).unwrap())
1357 }
1358
1359 fn reference_div(left: &RationalInteger, right: &RationalInteger) -> RationalInteger {
1360 let (ln, ld) = left.as_big_pair();
1361 let (rn, rd) = right.as_big_pair();
1362 fraction(ln.try_mul(&rd).unwrap(), ld.try_mul(&rn).unwrap())
1363 }
1364
1365 fn reference_pow(base: &RationalInteger, exponent: i64) -> RationalInteger {
1366 let (n, d) = base.as_big_pair();
1367 let magnitude = BigUint::try_from_u64(exponent.unsigned_abs()).unwrap();
1368 let (n, d) = (
1369 n.try_pow(&magnitude).unwrap(),
1370 d.try_pow(&magnitude).unwrap(),
1371 );
1372 if exponent < 0 {
1373 fraction(d, n)
1374 } else {
1375 fraction(n, d)
1376 }
1377 }
1378
1379 fn cap_sized_coprime_pair() -> (BigInt, BigInt) {
1381 let power = big_pow(2, 32_767);
1382 (big_add(&power, &big(-1)), big_add(&power, &big(1)))
1383 }
1384
1385 fn arithmetic_fixtures() -> Vec<RationalInteger> {
1386 vec![
1387 rational_zero(),
1388 rational_one(),
1389 rational_new(-1, 1),
1390 rational_new(3, 7),
1391 rational_new(-22, 15),
1392 RationalInteger::from_small(i128::MAX, 1).unwrap(),
1393 RationalInteger::from_small(i128::MIN, 1).unwrap(),
1394 RationalInteger::from_small(1, i128::MAX).unwrap(),
1395 RationalInteger::from_small(i128::MIN, i128::MAX).unwrap(),
1396 fraction(big_add(&big_pow(2, 200), &big(1)), big_pow(3, 100)),
1397 fraction(big_pow(-5, 91), big_pow(2, 150)),
1398 fraction(big_pow(6, 80), big_pow(35, 40)),
1399 fraction(big_pow(35, 40), big_pow(-6, 81)),
1400 fraction(big_pow(2, 130), big(3)),
1401 ]
1402 }
1403
1404 #[test]
1405 fn mul_div_pow_match_reduction_of_the_raw_result() {
1406 let fixtures = arithmetic_fixtures();
1407 for left in &fixtures {
1408 for right in &fixtures {
1409 let product = try_mul(left, right).unwrap();
1410 assert_canonical(&product);
1411 assert_eq!(product, reference_mul(left, right), "{left:?} * {right:?}");
1412 if right.numer_is_zero() {
1413 continue;
1414 }
1415 let quotient = try_div(left, right).unwrap();
1416 assert_canonical("ient);
1417 assert_eq!(quotient, reference_div(left, right), "{left:?} / {right:?}");
1418 }
1419 for exponent in -3..=3 {
1420 if exponent < 0 && left.numer_is_zero() {
1421 continue;
1422 }
1423 let power = try_pow_integer(left, &BigInt::from_i64(exponent)).unwrap();
1424 assert_canonical(&power);
1425 assert_eq!(
1426 power,
1427 reference_pow(left, exponent),
1428 "{left:?} ^ {exponent}"
1429 );
1430 }
1431 }
1432 }
1433
1434 #[test]
1435 fn try_div_keeps_denominator_positive_for_every_sign() {
1436 let (a, b) = cap_sized_coprime_pair();
1437 let positive = fraction(a.try_clone().unwrap(), big_pow(3, 50));
1438 let other = fraction(big_pow(7, 60), b);
1439 let negate = |value: &RationalInteger| try_sub(&rational_zero(), value).unwrap();
1440 for (left, right) in [
1441 (positive.clone(), negate(&other)),
1442 (negate(&positive), other.clone()),
1443 (negate(&positive), negate(&other)),
1444 (positive.clone(), other.clone()),
1445 ] {
1446 let quotient = try_div(&left, &right).unwrap();
1447 assert_canonical("ient);
1448 assert_eq!(quotient, reference_div(&left, &right));
1449 }
1450 assert_eq!(
1451 try_div(&positive, &negate(&positive)).unwrap(),
1452 rational_new(-1, 1)
1453 );
1454 }
1455
1456 #[test]
1457 fn power_of_a_reduced_fraction_performs_no_gcd() {
1458 let bases = [
1459 rational_new(7301, 7300),
1460 rational_new(-3, 7),
1461 fraction(big_add(&big_pow(2, 200), &big(1)), big_pow(3, 130)),
1462 fraction(big_pow(-5, 91), big_pow(2, 150)),
1463 ];
1464 for base in &bases {
1465 for exponent in [-301, -2, 2, 5, 200] {
1466 work_counters::reset();
1467 let power = try_pow_integer(base, &BigInt::from_i64(exponent)).unwrap();
1468 let counts = work_counters::snapshot();
1469 assert_eq!(counts.gcd_calls, 0, "{base:?} ^ {exponent}");
1470 assert_canonical(&power);
1471 assert_eq!(
1472 power,
1473 reference_pow(base, exponent),
1474 "{base:?} ^ {exponent}"
1475 );
1476 }
1477 }
1478 }
1479
1480 #[test]
1481 fn product_of_reciprocals_takes_gcds_of_the_operands_only() {
1482 let (a, b) = cap_sized_coprime_pair();
1483 let operand_bits = a.bits().max(b.bits());
1484 let left = fraction(a.try_clone().unwrap(), b.try_clone().unwrap());
1485 let right = fraction(b, a);
1486 work_counters::reset();
1487 assert_eq!(try_mul(&left, &right).unwrap(), rational_one());
1488 let counts = work_counters::snapshot();
1489 assert_eq!(counts.gcd_calls, 2);
1490 assert!(counts.gcd_max_operand_bits <= operand_bits);
1491 work_counters::reset();
1492 assert_eq!(try_div(&left, &left).unwrap(), rational_one());
1493 let counts = work_counters::snapshot();
1494 assert_eq!(counts.gcd_calls, 2);
1495 assert!(counts.gcd_max_operand_bits <= operand_bits);
1496 }
1497
1498 #[test]
1499 fn rational_zero_is_zero() {
1500 assert!(rational_is_zero(&rational_zero()));
1501 }
1502
1503 #[test]
1504 fn decimal_one_half_lifts_to_rational() {
1505 let decimal = Decimal::from_str("0.5").unwrap();
1506 let rational = decimal_to_rational(decimal).unwrap();
1507 assert_eq!(rational, rational_new(1, 2));
1508 }
1509
1510 #[test]
1511 fn try_to_decimal_one_third() {
1512 let rational = rational_new(1, 3);
1513 let decimal = rational.try_to_decimal().unwrap();
1514 let expected = Decimal::from_str("0.3333333333333333333333333333").unwrap();
1515 assert_eq!(decimal, expected);
1516 }
1517
1518 #[test]
1519 fn try_to_decimal_tiny_rounds_to_zero() {
1520 let rational =
1521 RationalInteger::from_bigint_strings("1", "1000000000000000000000000000000").unwrap();
1522 assert_eq!(rational.try_to_decimal().unwrap(), Decimal::ZERO);
1523 }
1524
1525 #[test]
1526 fn try_to_decimal_magnitude_beyond_max_returns_overflow() {
1527 let max = Decimal::MAX.normalize();
1528 let max_rational = decimal_to_rational(max).unwrap();
1529 let twice = try_mul(&max_rational, &rational_new(2, 1)).unwrap();
1530 assert_eq!(
1531 twice.try_to_decimal().unwrap_err(),
1532 NumericFailure::Overflow,
1533 );
1534 }
1535
1536 #[test]
1537 fn try_to_decimal_string_integer() {
1538 let rational = rational_new(37, 1);
1539 assert_eq!(rational.try_to_decimal_string().unwrap(), "37");
1540 }
1541
1542 #[test]
1543 fn display_str_shows_decimal_when_try_to_decimal_succeeds() {
1544 let rational = rational_new(355, 113);
1545 let display = rational.display_str();
1546 assert!(
1547 !display.contains('/'),
1548 "rational with successful try_to_decimal must display as decimal, got {display}"
1549 );
1550 }
1551
1552 #[test]
1553 fn try_to_decimal_huge_cancelling_rationals() {
1554 let numer = BigInt::try_from_str_radix("1", 10)
1555 .unwrap()
1556 .try_pow(&BigUint::try_from_u32(100).unwrap())
1557 .unwrap();
1558 let rational = try_rational_new(numer.clone(), numer).unwrap();
1559 assert_eq!(rational.try_to_decimal().unwrap(), Decimal::ONE);
1560 }
1561
1562 #[test]
1563 fn try_mul_integer() {
1564 let left = rational_new(50, 1);
1565 let right = rational_new(86400, 1);
1566 let product = try_mul(&left, &right).unwrap();
1567 assert_eq!(product, rational_new(4_320_000, 1));
1568 }
1569
1570 #[test]
1571 fn try_pow_negative_exponent_inverts_base() {
1572 let hour_factor = rational_new(3600, 1);
1573 let inverse = try_pow_integer(&hour_factor, &BigInt::from_i64(-1)).unwrap();
1574 assert_eq!(inverse, rational_new(1, 3600));
1575 }
1576
1577 #[test]
1578 fn rational_operation_divide_by_zero() {
1579 let left = rational_new(1, 1);
1580 let right = rational_new(0, 1);
1581 let failure = rational_operation(&left, NumericOperation::Divide, &right).unwrap_err();
1582 assert_eq!(failure, NumericFailure::DivisionByZero);
1583 }
1584
1585 #[test]
1586 fn rational_operation_power_irrational() {
1587 let base = rational_new(2, 1);
1588 let exponent = rational_new(1, 2);
1589 let failure = rational_operation(&base, NumericOperation::Power, &exponent).unwrap_err();
1590 assert_eq!(failure, NumericFailure::Irrational);
1591 }
1592
1593 #[test]
1594 fn rational_operation_power_exact() {
1595 let base = rational_new(4, 1);
1596 let exponent = rational_new(1, 2);
1597 let result = rational_operation(&base, NumericOperation::Power, &exponent).unwrap();
1598 assert_eq!(result, rational_new(2, 1));
1599 }
1600
1601 #[test]
1602 fn rational_power_exact_roots_of_rationals() {
1603 let power = |base: RationalInteger, exponent: RationalInteger| {
1604 rational_operation(&base, NumericOperation::Power, &exponent)
1605 };
1606 assert_eq!(
1607 power(rational_new(9, 4), rational_new(1, 2)).unwrap(),
1608 rational_new(3, 2)
1609 );
1610 assert_eq!(
1611 power(rational_new(9, 4), rational_new(-1, 2)).unwrap(),
1612 rational_new(2, 3)
1613 );
1614 assert_eq!(
1615 power(rational_new(8, 1), rational_new(2, 3)).unwrap(),
1616 rational_new(4, 1)
1617 );
1618 assert_eq!(
1619 power(rational_new(-8, 1), rational_new(1, 3)).unwrap(),
1620 rational_new(-2, 1)
1621 );
1622 assert_eq!(
1623 power(rational_new(-4, 1), rational_new(2, 1)).unwrap(),
1624 rational_new(16, 1)
1625 );
1626 assert_eq!(
1627 power(rational_new(1, 1), rational_new(49_595, 100_000)).unwrap(),
1628 rational_new(1, 1)
1629 );
1630 }
1631
1632 #[test]
1633 fn power_with_huge_integer_exponent_of_one_and_minus_one() {
1634 let even_huge =
1635 RationalInteger::from_bigint_strings("1000000000000000000000000000000", "1").unwrap();
1636 let odd_huge =
1637 RationalInteger::from_bigint_strings("1000000000000000000000000000001", "1").unwrap();
1638 assert_eq!(
1639 rational_operation(&rational_new(1, 1), NumericOperation::Power, &even_huge).unwrap(),
1640 rational_new(1, 1)
1641 );
1642 assert_eq!(
1643 rational_operation(&rational_new(-1, 1), NumericOperation::Power, &odd_huge).unwrap(),
1644 rational_new(-1, 1)
1645 );
1646 }
1647
1648 #[test]
1649 fn power_with_huge_root_degree_above_bit_length_is_irrational() {
1650 let degree = RationalInteger::from_bigint_strings("1", "1000000000000").unwrap();
1651 assert_eq!(
1652 rational_operation(&rational_new(2, 1), NumericOperation::Power, °ree).unwrap_err(),
1653 NumericFailure::Irrational
1654 );
1655 }
1656
1657 #[test]
1658 fn power_with_exponent_beyond_every_allocation_is_out_of_memory() {
1659 let exponent = BigInt::from_i128(1i128 << 70);
1660 assert_eq!(
1661 try_pow_integer(&rational_new(2, 1), &exponent).unwrap_err(),
1662 NumericFailure::OutOfMemory
1663 );
1664 assert_eq!(
1665 try_pow_integer(&rational_new(1, 1), &exponent).unwrap(),
1666 rational_new(1, 1)
1667 );
1668 }
1669
1670 #[test]
1671 fn rational_power_irrational_fails_fast_for_large_degree() {
1672 let failure = rational_operation(
1673 &rational_new(480, 1),
1674 NumericOperation::Power,
1675 &rational_new(49_595, 100_000),
1676 )
1677 .unwrap_err();
1678 assert_eq!(failure, NumericFailure::Irrational);
1679 let failure = rational_operation(
1680 &rational_new(2, 1),
1681 NumericOperation::Power,
1682 &rational_new(49_595, 100_000),
1683 )
1684 .unwrap_err();
1685 assert_eq!(failure, NumericFailure::Irrational);
1686 }
1687
1688 #[test]
1689 fn rational_power_negative_base_even_root_is_irrational() {
1690 let failure = rational_operation(
1691 &rational_new(-4, 1),
1692 NumericOperation::Power,
1693 &rational_new(1, 2),
1694 )
1695 .unwrap_err();
1696 assert_eq!(failure, NumericFailure::Irrational);
1697 }
1698
1699 #[test]
1700 fn rational_operation_add() {
1701 let left = rational_new(1, 3);
1702 let right = rational_new(1, 6);
1703 let sum = rational_operation(&left, NumericOperation::Add, &right).unwrap();
1704 assert_eq!(sum, rational_new(1, 2));
1705 }
1706
1707 #[test]
1708 fn rational_operation_with_fallback_add_exact_rational() {
1709 let left = rational_new(1, 3);
1710 let right = rational_new(1, 6);
1711 let sum = rational_operation_with_fallback(&left, NumericOperation::Add, &right).unwrap();
1712 assert_eq!(sum, rational_new(1, 2));
1713 }
1714
1715 #[test]
1716 fn rational_operation_with_fallback_power_sqrt_via_decimal() {
1717 let result = rational_operation_with_fallback(
1718 &rational_new(2, 1),
1719 NumericOperation::Power,
1720 &rational_new(1, 2),
1721 )
1722 .unwrap();
1723 let expected = rational_new(2, 1).try_to_decimal().unwrap().sqrt().unwrap();
1724 assert_eq!(
1725 result.try_to_decimal().unwrap().round_dp(27),
1726 expected.round_dp(27),
1727 );
1728 }
1729
1730 #[test]
1731 fn rational_modulo_is_floored_and_takes_divisor_sign() {
1732 assert_eq!(
1733 operate(
1734 &rational_new(-7, 1),
1735 NumericOperation::Modulo,
1736 &rational_new(3, 1)
1737 )
1738 .unwrap(),
1739 rational_new(2, 1)
1740 );
1741 assert_eq!(
1742 operate(
1743 &rational_new(7, 1),
1744 NumericOperation::Modulo,
1745 &rational_new(-3, 1)
1746 )
1747 .unwrap(),
1748 rational_new(-2, 1)
1749 );
1750 assert_eq!(
1751 operate(
1752 &rational_new(-7, 2),
1753 NumericOperation::Modulo,
1754 &rational_new(1, 1)
1755 )
1756 .unwrap(),
1757 rational_new(1, 2)
1758 );
1759 assert_eq!(
1760 operate(
1761 &rational_new(-7, 1),
1762 NumericOperation::Modulo,
1763 &rational_new(-3, 1)
1764 )
1765 .unwrap(),
1766 rational_new(-1, 1)
1767 );
1768 }
1769
1770 #[test]
1771 fn rational_abs_negates_negative_numerator() {
1772 let negative = rational_new(-172_800, 1);
1773 assert_eq!(rational_abs(&negative).unwrap(), rational_new(172_800, 1));
1774 }
1775
1776 #[test]
1777 fn try_to_decimal_string_rejects_magnitude_overflow() {
1778 let too_large =
1779 RationalInteger::from_bigint_strings("10000000000000000000000000000000", "1").unwrap();
1780 assert_eq!(
1781 too_large.try_to_decimal().unwrap_err(),
1782 NumericFailure::Overflow
1783 );
1784 assert_eq!(
1785 too_large.try_to_decimal_string().unwrap_err(),
1786 NumericFailure::Overflow,
1787 );
1788 }
1789
1790 #[test]
1791 fn decimal_max_times_decimal_max_stays_exact_without_decimal_fallback() {
1792 let max_decimal = Decimal::MAX.normalize();
1793 let left = decimal_to_rational(max_decimal).unwrap();
1794 let right = decimal_to_rational(max_decimal).unwrap();
1795 let product = rational_operation(&left, NumericOperation::Multiply, &right).unwrap();
1796 let expected = try_mul(&left, &right).unwrap();
1797 assert_eq!(product, expected);
1798 }
1799
1800 #[test]
1801 fn alloc_error_maps_to_out_of_memory() {
1802 assert_eq!(
1803 NumericFailure::from(crate::computation::bigint::AllocError),
1804 NumericFailure::OutOfMemory
1805 );
1806 }
1807
1808 #[test]
1809 fn small_path_basic_arithmetic() {
1810 let a = rational_new(3, 1);
1811 let b = rational_new(5, 1);
1812 assert_eq!(try_add(&a, &b).unwrap(), rational_new(8, 1));
1813 assert_eq!(try_sub(&a, &b).unwrap(), rational_new(-2, 1));
1814 assert_eq!(try_mul(&a, &b).unwrap(), rational_new(15, 1));
1815 assert_eq!(try_div(&a, &b).unwrap(), rational_new(3, 5));
1816 }
1817
1818 #[test]
1819 fn small_path_reduces() {
1820 let r = rational_new(6, 4);
1821 assert_eq!(r, rational_new(3, 2));
1822 }
1823
1824 #[test]
1825 fn small_path_try_to_decimal_integer() {
1826 let r = rational_new(42, 1);
1827 assert_eq!(r.try_to_decimal().unwrap(), Decimal::from(42));
1828 }
1829}