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 if base.is_sign_negative() {
840 return Err(NumericFailure::Irrational);
841 }
842 base.checked_powd(exponent).ok_or(NumericFailure::Overflow)
843}
844
845pub fn try_add(
848 left: &RationalInteger,
849 right: &RationalInteger,
850) -> Result<RationalInteger, NumericFailure> {
851 if let (
852 Repr::Small {
853 numer: ln,
854 denom: ld,
855 },
856 Repr::Small {
857 numer: rn,
858 denom: rd,
859 },
860 ) = (&left.repr, &right.repr)
861 {
862 if let (Some(a), Some(b), Some(d)) = (
864 ln.checked_mul(*rd),
865 rn.checked_mul(*ld),
866 ld.checked_mul(*rd),
867 ) {
868 if let Some(n) = a.checked_add(b) {
869 return RationalInteger::from_small(n, d);
870 }
871 }
872 }
873 let (ln, ld) = left.as_big_pair();
875 let (rn, rd) = right.as_big_pair();
876 let numerator = ln.try_mul(&rd)?.try_add(&rn.try_mul(&ld)?)?;
877 let denominator = ld.try_mul(&rd)?;
878 try_rational_new(numerator, denominator)
879}
880
881pub fn try_sub(
882 left: &RationalInteger,
883 right: &RationalInteger,
884) -> Result<RationalInteger, NumericFailure> {
885 if let (
886 Repr::Small {
887 numer: ln,
888 denom: ld,
889 },
890 Repr::Small {
891 numer: rn,
892 denom: rd,
893 },
894 ) = (&left.repr, &right.repr)
895 {
896 if let (Some(a), Some(b), Some(d)) = (
897 ln.checked_mul(*rd),
898 rn.checked_mul(*ld),
899 ld.checked_mul(*rd),
900 ) {
901 if let Some(n) = a.checked_sub(b) {
902 return RationalInteger::from_small(n, d);
903 }
904 }
905 }
906 let (ln, ld) = left.as_big_pair();
907 let (rn, rd) = right.as_big_pair();
908 let numerator = ln.try_mul(&rd)?.try_sub(&rn.try_mul(&ld)?)?;
909 let denominator = ld.try_mul(&rd)?;
910 try_rational_new(numerator, denominator)
911}
912
913pub fn try_mul(
914 left: &RationalInteger,
915 right: &RationalInteger,
916) -> Result<RationalInteger, NumericFailure> {
917 if let (
918 Repr::Small {
919 numer: ln,
920 denom: ld,
921 },
922 Repr::Small {
923 numer: rn,
924 denom: rd,
925 },
926 ) = (&left.repr, &right.repr)
927 {
928 if let (Some(n), Some(d)) = (ln.checked_mul(*rn), ld.checked_mul(*rd)) {
929 return RationalInteger::from_small(n, d);
930 }
931 }
932 if left.numer_is_zero() || right.numer_is_zero() {
933 return Ok(rational_zero());
934 }
935 let (ln, ld) = left.as_big_pair();
936 let (rn, rd) = right.as_big_pair();
937 cross_cancelled_product((&ln, &ld), (&rn, &rd))
938}
939
940fn cross_cancelled_product(
944 (ln, ld): (&BigInt, &BigInt),
945 (rn, rd): (&BigInt, &BigInt),
946) -> Result<RationalInteger, NumericFailure> {
947 let left_cross = ln.try_gcd(rd)?;
948 let right_cross = rn.try_gcd(ld)?;
949 let numerator = ln
950 .try_div_trunc(&left_cross)?
951 .try_mul(&rn.try_div_trunc(&right_cross)?)?;
952 let denominator = ld
953 .try_div_trunc(&right_cross)?
954 .try_mul(&rd.try_div_trunc(&left_cross)?)?;
955 Ok(RationalInteger::from_coprime(numerator, denominator))
956}
957
958pub fn try_div(
959 left: &RationalInteger,
960 right: &RationalInteger,
961) -> Result<RationalInteger, NumericFailure> {
962 if right.numer_is_zero() {
963 return Err(NumericFailure::DivisionByZero);
964 }
965 if let (
966 Repr::Small {
967 numer: ln,
968 denom: ld,
969 },
970 Repr::Small {
971 numer: rn,
972 denom: rd,
973 },
974 ) = (&left.repr, &right.repr)
975 {
976 if let (Some(n), Some(d)) = (ln.checked_mul(*rd), ld.checked_mul(*rn)) {
977 return RationalInteger::from_small(n, d);
978 }
979 }
980 if left.numer_is_zero() {
981 return Ok(rational_zero());
982 }
983 let (ln, ld) = left.as_big_pair();
984 let (rn, rd) = right.try_reciprocal_pair()?;
985 cross_cancelled_product((&ln, &ld), (&rn, &rd))
986}
987
988pub fn try_pow_integer(
990 base: &RationalInteger,
991 exponent: &BigInt,
992) -> Result<RationalInteger, NumericFailure> {
993 if exponent.is_zero() {
994 return Ok(rational_one());
995 }
996 if exponent.is_negative() {
997 try_pow_magnitude(&base.try_reciprocal()?, exponent.magnitude())
998 } else {
999 try_pow_magnitude(base, exponent.magnitude())
1000 }
1001}
1002
1003fn try_pow_magnitude(
1006 base: &RationalInteger,
1007 exponent: &BigUint,
1008) -> Result<RationalInteger, NumericFailure> {
1009 if let (Repr::Small { numer, denom }, Some(small_exponent)) = (&base.repr, exponent.to_u32()) {
1010 if let (Some(n), Some(d)) = (
1011 numer.checked_pow(small_exponent),
1012 denom.checked_pow(small_exponent),
1013 ) {
1014 return Ok(RationalInteger::from_reduced_small(n, d));
1015 }
1016 }
1017 let (numer, denom) = base.as_big_pair();
1018 Ok(RationalInteger::from_coprime(
1019 numer.try_pow(exponent)?,
1020 denom.try_pow(exponent)?,
1021 ))
1022}
1023
1024pub fn try_rational_power(
1025 base: &RationalInteger,
1026 exponent: &RationalInteger,
1027) -> Result<RationalInteger, NumericFailure> {
1028 if !exponent.numer_is_positive() && !exponent.numer_is_negative() && exponent.numer_is_zero() {
1029 return Ok(rational_one());
1030 }
1031
1032 if exponent.is_integer() {
1033 let integer_exponent = exponent.to_big_numer();
1034 if power_exceeds_magnitude_limit(base, integer_exponent.magnitude()) {
1035 return Err(NumericFailure::MagnitudeLimit);
1036 }
1037 return try_pow_integer(base, &integer_exponent);
1038 }
1039
1040 if base.numer_is_zero() {
1041 if exponent.numer_is_negative() || exponent.numer_is_zero() {
1042 return Err(NumericFailure::DivisionByZero);
1043 }
1044 return Ok(rational_new(0, 1));
1045 }
1046
1047 let root_degree = exponent.to_big_denom().magnitude().clone();
1048 let abs_exponent = exponent.to_big_numer();
1049
1050 let result_negative = base.numer_is_negative() && abs_exponent.magnitude().is_odd();
1051 if result_negative && !root_degree.is_odd() {
1052 return Err(NumericFailure::Irrational);
1053 }
1054
1055 let numer_root = exact_integer_root(&base.to_big_numer().try_abs()?, &root_degree)?;
1056 let denom_root = exact_integer_root(&base.to_big_denom(), &root_degree)?;
1057 let signed_numer_root = if result_negative {
1058 numer_root.try_neg()?
1059 } else {
1060 numer_root
1061 };
1062 let root = RationalInteger::from_coprime(signed_numer_root, denom_root);
1063 if power_exceeds_magnitude_limit(&root, abs_exponent.magnitude()) {
1064 return Err(NumericFailure::MagnitudeLimit);
1065 }
1066 let raised = try_pow_magnitude(&root, abs_exponent.magnitude())?;
1067
1068 if exponent.numer_is_negative() {
1069 raised.try_reciprocal()
1070 } else {
1071 Ok(raised)
1072 }
1073}
1074
1075fn power_exceeds_magnitude_limit(base: &RationalInteger, exponent: &BigUint) -> bool {
1080 [base.numer_magnitude_bits(), base.denom_magnitude_bits()]
1081 .into_iter()
1082 .filter(|bits| *bits >= 2)
1083 .any(|bits| match exponent.to_u32() {
1084 Some(exponent) => (bits - 1)
1085 .checked_mul(u64::from(exponent))
1086 .is_none_or(|lower_bound| lower_bound >= MAX_RATIONAL_BITS),
1087 None => true,
1088 })
1089}
1090
1091fn exact_integer_root(value: &BigInt, degree: &BigUint) -> Result<BigInt, NumericFailure> {
1093 let root = value.try_nth_root(degree)?;
1094 if root.try_pow(degree)? == *value {
1095 Ok(root)
1096 } else {
1097 Err(NumericFailure::Irrational)
1098 }
1099}
1100
1101pub use {try_div as checked_div, try_mul as checked_mul, try_pow_integer as checked_pow_integer};
1102
1103#[cfg(test)]
1108mod tests {
1109 use super::*;
1110 use rust_decimal::Decimal;
1111 use rust_decimal::MathematicalOps;
1112 use std::str::FromStr;
1113
1114 #[test]
1115 fn power_of_two_with_all_ones_exponent_is_positive() {
1116 let two_pow_127 =
1117 RationalInteger::from_bigint_strings("170141183460469231731687303715884105728", "1")
1118 .unwrap();
1119 assert!(two_pow_127.numer_is_positive(), "parsed 2^127 sign");
1120 let big =
1121 BigInt::try_from_str_radix("170141183460469231731687303715884105728", 10).unwrap();
1122 assert!(big.is_positive(), "BigInt 2^127 sign");
1123 let product = try_mul(&rational_new(1 << 62, 1), &rational_new(1 << 62, 1)).unwrap();
1124 let product = try_mul(&product, &rational_new(8, 1)).unwrap();
1125 assert!(product.numer_is_positive(), "2^62 * 2^62 * 8 sign");
1126 let powered = try_pow_integer(&rational_new(2, 1), &BigInt::from_i64(127)).unwrap();
1127 assert!(powered.numer_is_positive(), "2^127 via power sign");
1128 assert_eq!(powered, two_pow_127);
1129 }
1130
1131 #[test]
1132 fn i128_min_numerator_promotes_instead_of_failing() {
1133 let two_pow_127 =
1134 RationalInteger::from_bigint_strings("170141183460469231731687303715884105728", "1")
1135 .unwrap();
1136 let min = RationalInteger::from_small(i128::MIN, 1).unwrap();
1137 assert_eq!(rational_abs(&min).unwrap(), two_pow_127);
1138 assert_eq!(try_div(&min, &rational_new(-1, 1)).unwrap(), two_pow_127);
1139 let reciprocal = try_div(&rational_new(1, 1), &min).unwrap();
1140 assert_eq!(
1141 reciprocal,
1142 RationalInteger::from_bigint_strings("-1", "170141183460469231731687303715884105728")
1143 .unwrap()
1144 );
1145 assert_eq!(
1146 RationalInteger::from_small(i128::MIN, -2).unwrap(),
1147 RationalInteger::from_bigint_strings("85070591730234615865843651857942052864", "1")
1148 .unwrap()
1149 );
1150 assert_eq!(
1151 RationalInteger::from_small(1, i128::MIN).unwrap(),
1152 RationalInteger::from_bigint_strings("-1", "170141183460469231731687303715884105728")
1153 .unwrap()
1154 );
1155 assert_eq!(
1156 RationalInteger::from_small(i128::MIN, i128::MIN).unwrap(),
1157 rational_one()
1158 );
1159 assert_eq!(
1160 RationalInteger::from_small(3, i128::MIN).unwrap(),
1161 RationalInteger::from_bigint_strings("-3", "170141183460469231731687303715884105728")
1162 .unwrap()
1163 );
1164 }
1165
1166 fn two_pow(exponent: i64) -> RationalInteger {
1167 try_pow_integer(&rational_new(2, 1), &BigInt::from_i64(exponent)).unwrap()
1168 }
1169
1170 fn operate(
1171 left: &RationalInteger,
1172 operation: NumericOperation,
1173 right: &RationalInteger,
1174 ) -> Result<RationalInteger, NumericFailure> {
1175 rational_operation(left, operation, right)
1176 }
1177
1178 fn negate(value: &RationalInteger) -> RationalInteger {
1179 try_sub(&rational_zero(), value).unwrap()
1180 }
1181
1182 const LIMIT: i64 = MAX_RATIONAL_BITS as i64;
1183
1184 #[test]
1185 fn rational_operation_accepts_results_at_the_bit_limit() {
1186 let at_limit = two_pow(LIMIT - 1);
1187 assert_eq!(at_limit.numer_magnitude_bits(), MAX_RATIONAL_BITS);
1188 let half_limit = two_pow(LIMIT / 2 - 1);
1189 assert_eq!(
1190 operate(&half_limit, NumericOperation::Multiply, &two_pow(LIMIT / 2)).unwrap(),
1191 at_limit
1192 );
1193 assert_eq!(
1194 operate(
1195 &two_pow(LIMIT - 2),
1196 NumericOperation::Add,
1197 &two_pow(LIMIT - 2)
1198 )
1199 .unwrap(),
1200 at_limit
1201 );
1202 let reciprocal = operate(&rational_one(), NumericOperation::Divide, &at_limit).unwrap();
1203 assert_eq!(reciprocal.denom_magnitude_bits(), MAX_RATIONAL_BITS);
1204 }
1205
1206 #[test]
1207 fn rational_operation_rejects_numerator_beyond_the_bit_limit() {
1208 let at_limit = two_pow(LIMIT - 1);
1209 let two = rational_new(2, 1);
1210 for (operation, right) in [
1211 (NumericOperation::Add, at_limit.clone()),
1212 (NumericOperation::Subtract, negate(&at_limit)),
1213 (NumericOperation::Multiply, two.clone()),
1214 (NumericOperation::Divide, rational_new(1, 2)),
1215 (NumericOperation::Power, two.clone()),
1216 ] {
1217 assert_eq!(
1218 operate(&at_limit, operation, &right).unwrap_err(),
1219 NumericFailure::MagnitudeLimit,
1220 "{operation:?}"
1221 );
1222 }
1223 }
1224
1225 #[test]
1226 fn rational_operation_rejects_denominator_beyond_the_bit_limit() {
1227 let reciprocal_at_limit = try_div(&rational_one(), &two_pow(LIMIT - 1)).unwrap();
1228 assert_eq!(
1229 operate(
1230 &reciprocal_at_limit,
1231 NumericOperation::Divide,
1232 &rational_new(2, 1)
1233 )
1234 .unwrap_err(),
1235 NumericFailure::MagnitudeLimit
1236 );
1237 assert_eq!(
1238 operate(
1239 &reciprocal_at_limit,
1240 NumericOperation::Multiply,
1241 &rational_new(1, 2)
1242 )
1243 .unwrap_err(),
1244 NumericFailure::MagnitudeLimit
1245 );
1246 }
1247
1248 #[test]
1249 fn power_precheck_is_exact_at_the_bit_limit() {
1250 let two = rational_new(2, 1);
1251 assert_eq!(
1252 operate(&two, NumericOperation::Power, &rational_new(LIMIT - 1, 1)).unwrap(),
1253 two_pow(LIMIT - 1)
1254 );
1255 assert!(!power_exceeds_magnitude_limit(
1256 &two,
1257 &BigUint::try_from_u32(LIMIT as u32 - 1).unwrap()
1258 ));
1259 assert!(power_exceeds_magnitude_limit(
1260 &two,
1261 &BigUint::try_from_u32(LIMIT as u32).unwrap()
1262 ));
1263 let two_pow_sixteen = rational_new(1 << 16, 1);
1264 let largest_fitting_exponent = (LIMIT as u32 - 1) / 16;
1265 assert!(!power_exceeds_magnitude_limit(
1266 &two_pow_sixteen,
1267 &BigUint::try_from_u32(largest_fitting_exponent).unwrap()
1268 ));
1269 assert_eq!(
1270 operate(
1271 &two_pow_sixteen,
1272 NumericOperation::Power,
1273 &rational_new(i64::from(largest_fitting_exponent), 1)
1274 )
1275 .unwrap(),
1276 two_pow(16 * i64::from(largest_fitting_exponent))
1277 );
1278 }
1279
1280 #[test]
1281 fn power_precheck_rejects_huge_exponents_before_allocating() {
1282 let huge = RationalInteger::from_bigint_strings("1180591620717411303424", "1").unwrap();
1283 for base in [rational_new(2, 1), rational_new(1, 2), rational_new(-3, 7)] {
1284 assert_eq!(
1285 operate(&base, NumericOperation::Power, &huge).unwrap_err(),
1286 NumericFailure::MagnitudeLimit,
1287 "{base:?}"
1288 );
1289 assert_eq!(
1290 operate(&base, NumericOperation::Power, &negate(&huge)).unwrap_err(),
1291 NumericFailure::MagnitudeLimit,
1292 "{base:?} negative exponent"
1293 );
1294 }
1295 assert_eq!(
1296 operate(&rational_new(-1, 1), NumericOperation::Power, &huge).unwrap(),
1297 rational_one()
1298 );
1299 }
1300
1301 #[test]
1302 fn power_precheck_applies_to_the_root_of_fractional_exponents() {
1303 let four = rational_new(4, 1);
1304 assert_eq!(
1305 operate(&four, NumericOperation::Power, &rational_new(LIMIT - 1, 2)).unwrap(),
1306 two_pow(LIMIT - 1)
1307 );
1308 assert_eq!(
1309 operate(&four, NumericOperation::Power, &rational_new(LIMIT + 1, 2)).unwrap_err(),
1310 NumericFailure::MagnitudeLimit
1311 );
1312 }
1313
1314 use crate::computation::bigint::work_counters;
1315
1316 fn big(value: i64) -> BigInt {
1317 BigInt::from_i64(value)
1318 }
1319
1320 fn big_pow(base: i64, exponent: u32) -> BigInt {
1321 big(base)
1322 .try_pow(&BigUint::try_from_u32(exponent).unwrap())
1323 .unwrap()
1324 }
1325
1326 fn big_add(left: &BigInt, right: &BigInt) -> BigInt {
1327 left.try_add(right).unwrap()
1328 }
1329
1330 fn fraction(numer: BigInt, denom: BigInt) -> RationalInteger {
1331 try_rational_new(numer, denom).unwrap()
1332 }
1333
1334 fn assert_canonical(value: &RationalInteger) {
1335 match &value.repr {
1336 Repr::Small { numer, denom } => {
1337 assert!(*denom > 0, "{value:?} denominator must be positive");
1338 assert_eq!(
1339 reduce_small(*numer, *denom),
1340 (*numer, *denom),
1341 "{value:?} is not reduced"
1342 );
1343 }
1344 Repr::Big { numer, denom } => {
1345 assert!(
1346 denom.is_positive(),
1347 "{value:?} denominator must be positive"
1348 );
1349 assert!(
1350 numer.to_i128().is_none() || denom.to_i128().is_none(),
1351 "{value:?} fits i128 and must be Small"
1352 );
1353 let reduced = RationalInteger::try_reduce_ref(value).expect("reducible");
1354 assert!(&reduced == value, "{value:?} is not reduced");
1355 }
1356 }
1357 }
1358
1359 fn reference_mul(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(&rn).unwrap(), ld.try_mul(&rd).unwrap())
1363 }
1364
1365 fn reference_div(left: &RationalInteger, right: &RationalInteger) -> RationalInteger {
1366 let (ln, ld) = left.as_big_pair();
1367 let (rn, rd) = right.as_big_pair();
1368 fraction(ln.try_mul(&rd).unwrap(), ld.try_mul(&rn).unwrap())
1369 }
1370
1371 fn reference_pow(base: &RationalInteger, exponent: i64) -> RationalInteger {
1372 let (n, d) = base.as_big_pair();
1373 let magnitude = BigUint::try_from_u64(exponent.unsigned_abs()).unwrap();
1374 let (n, d) = (
1375 n.try_pow(&magnitude).unwrap(),
1376 d.try_pow(&magnitude).unwrap(),
1377 );
1378 if exponent < 0 {
1379 fraction(d, n)
1380 } else {
1381 fraction(n, d)
1382 }
1383 }
1384
1385 fn cap_sized_coprime_pair() -> (BigInt, BigInt) {
1387 let power = big_pow(2, 32_767);
1388 (big_add(&power, &big(-1)), big_add(&power, &big(1)))
1389 }
1390
1391 fn arithmetic_fixtures() -> Vec<RationalInteger> {
1392 vec![
1393 rational_zero(),
1394 rational_one(),
1395 rational_new(-1, 1),
1396 rational_new(3, 7),
1397 rational_new(-22, 15),
1398 RationalInteger::from_small(i128::MAX, 1).unwrap(),
1399 RationalInteger::from_small(i128::MIN, 1).unwrap(),
1400 RationalInteger::from_small(1, i128::MAX).unwrap(),
1401 RationalInteger::from_small(i128::MIN, i128::MAX).unwrap(),
1402 fraction(big_add(&big_pow(2, 200), &big(1)), big_pow(3, 100)),
1403 fraction(big_pow(-5, 91), big_pow(2, 150)),
1404 fraction(big_pow(6, 80), big_pow(35, 40)),
1405 fraction(big_pow(35, 40), big_pow(-6, 81)),
1406 fraction(big_pow(2, 130), big(3)),
1407 ]
1408 }
1409
1410 #[test]
1411 fn mul_div_pow_match_reduction_of_the_raw_result() {
1412 let fixtures = arithmetic_fixtures();
1413 for left in &fixtures {
1414 for right in &fixtures {
1415 let product = try_mul(left, right).unwrap();
1416 assert_canonical(&product);
1417 assert_eq!(product, reference_mul(left, right), "{left:?} * {right:?}");
1418 if right.numer_is_zero() {
1419 continue;
1420 }
1421 let quotient = try_div(left, right).unwrap();
1422 assert_canonical("ient);
1423 assert_eq!(quotient, reference_div(left, right), "{left:?} / {right:?}");
1424 }
1425 for exponent in -3..=3 {
1426 if exponent < 0 && left.numer_is_zero() {
1427 continue;
1428 }
1429 let power = try_pow_integer(left, &BigInt::from_i64(exponent)).unwrap();
1430 assert_canonical(&power);
1431 assert_eq!(
1432 power,
1433 reference_pow(left, exponent),
1434 "{left:?} ^ {exponent}"
1435 );
1436 }
1437 }
1438 }
1439
1440 #[test]
1441 fn try_div_keeps_denominator_positive_for_every_sign() {
1442 let (a, b) = cap_sized_coprime_pair();
1443 let positive = fraction(a.try_clone().unwrap(), big_pow(3, 50));
1444 let other = fraction(big_pow(7, 60), b);
1445 let negate = |value: &RationalInteger| try_sub(&rational_zero(), value).unwrap();
1446 for (left, right) in [
1447 (positive.clone(), negate(&other)),
1448 (negate(&positive), other.clone()),
1449 (negate(&positive), negate(&other)),
1450 (positive.clone(), other.clone()),
1451 ] {
1452 let quotient = try_div(&left, &right).unwrap();
1453 assert_canonical("ient);
1454 assert_eq!(quotient, reference_div(&left, &right));
1455 }
1456 assert_eq!(
1457 try_div(&positive, &negate(&positive)).unwrap(),
1458 rational_new(-1, 1)
1459 );
1460 }
1461
1462 #[test]
1463 fn power_of_a_reduced_fraction_performs_no_gcd() {
1464 let bases = [
1465 rational_new(7301, 7300),
1466 rational_new(-3, 7),
1467 fraction(big_add(&big_pow(2, 200), &big(1)), big_pow(3, 130)),
1468 fraction(big_pow(-5, 91), big_pow(2, 150)),
1469 ];
1470 for base in &bases {
1471 for exponent in [-301, -2, 2, 5, 200] {
1472 work_counters::reset();
1473 let power = try_pow_integer(base, &BigInt::from_i64(exponent)).unwrap();
1474 let counts = work_counters::snapshot();
1475 assert_eq!(counts.gcd_calls, 0, "{base:?} ^ {exponent}");
1476 assert_canonical(&power);
1477 assert_eq!(
1478 power,
1479 reference_pow(base, exponent),
1480 "{base:?} ^ {exponent}"
1481 );
1482 }
1483 }
1484 }
1485
1486 #[test]
1487 fn product_of_reciprocals_takes_gcds_of_the_operands_only() {
1488 let (a, b) = cap_sized_coprime_pair();
1489 let operand_bits = a.bits().max(b.bits());
1490 let left = fraction(a.try_clone().unwrap(), b.try_clone().unwrap());
1491 let right = fraction(b, a);
1492 work_counters::reset();
1493 assert_eq!(try_mul(&left, &right).unwrap(), rational_one());
1494 let counts = work_counters::snapshot();
1495 assert_eq!(counts.gcd_calls, 2);
1496 assert!(counts.gcd_max_operand_bits <= operand_bits);
1497 work_counters::reset();
1498 assert_eq!(try_div(&left, &left).unwrap(), rational_one());
1499 let counts = work_counters::snapshot();
1500 assert_eq!(counts.gcd_calls, 2);
1501 assert!(counts.gcd_max_operand_bits <= operand_bits);
1502 }
1503
1504 #[test]
1505 fn rational_zero_is_zero() {
1506 assert!(rational_is_zero(&rational_zero()));
1507 }
1508
1509 #[test]
1510 fn decimal_one_half_lifts_to_rational() {
1511 let decimal = Decimal::from_str("0.5").unwrap();
1512 let rational = decimal_to_rational(decimal).unwrap();
1513 assert_eq!(rational, rational_new(1, 2));
1514 }
1515
1516 #[test]
1517 fn try_to_decimal_one_third() {
1518 let rational = rational_new(1, 3);
1519 let decimal = rational.try_to_decimal().unwrap();
1520 let expected = Decimal::from_str("0.3333333333333333333333333333").unwrap();
1521 assert_eq!(decimal, expected);
1522 }
1523
1524 #[test]
1525 fn try_to_decimal_tiny_rounds_to_zero() {
1526 let rational =
1527 RationalInteger::from_bigint_strings("1", "1000000000000000000000000000000").unwrap();
1528 assert_eq!(rational.try_to_decimal().unwrap(), Decimal::ZERO);
1529 }
1530
1531 #[test]
1532 fn try_to_decimal_magnitude_beyond_max_returns_overflow() {
1533 let max = Decimal::MAX.normalize();
1534 let max_rational = decimal_to_rational(max).unwrap();
1535 let twice = try_mul(&max_rational, &rational_new(2, 1)).unwrap();
1536 assert_eq!(
1537 twice.try_to_decimal().unwrap_err(),
1538 NumericFailure::Overflow,
1539 );
1540 }
1541
1542 #[test]
1543 fn try_to_decimal_string_integer() {
1544 let rational = rational_new(37, 1);
1545 assert_eq!(rational.try_to_decimal_string().unwrap(), "37");
1546 }
1547
1548 #[test]
1549 fn display_str_shows_decimal_when_try_to_decimal_succeeds() {
1550 let rational = rational_new(355, 113);
1551 let display = rational.display_str();
1552 assert!(
1553 !display.contains('/'),
1554 "rational with successful try_to_decimal must display as decimal, got {display}"
1555 );
1556 }
1557
1558 #[test]
1559 fn try_to_decimal_huge_cancelling_rationals() {
1560 let numer = BigInt::try_from_str_radix("1", 10)
1561 .unwrap()
1562 .try_pow(&BigUint::try_from_u32(100).unwrap())
1563 .unwrap();
1564 let rational = try_rational_new(numer.clone(), numer).unwrap();
1565 assert_eq!(rational.try_to_decimal().unwrap(), Decimal::ONE);
1566 }
1567
1568 #[test]
1569 fn try_mul_integer() {
1570 let left = rational_new(50, 1);
1571 let right = rational_new(86400, 1);
1572 let product = try_mul(&left, &right).unwrap();
1573 assert_eq!(product, rational_new(4_320_000, 1));
1574 }
1575
1576 #[test]
1577 fn try_pow_negative_exponent_inverts_base() {
1578 let hour_factor = rational_new(3600, 1);
1579 let inverse = try_pow_integer(&hour_factor, &BigInt::from_i64(-1)).unwrap();
1580 assert_eq!(inverse, rational_new(1, 3600));
1581 }
1582
1583 #[test]
1584 fn rational_operation_divide_by_zero() {
1585 let left = rational_new(1, 1);
1586 let right = rational_new(0, 1);
1587 let failure = rational_operation(&left, NumericOperation::Divide, &right).unwrap_err();
1588 assert_eq!(failure, NumericFailure::DivisionByZero);
1589 }
1590
1591 #[test]
1592 fn rational_operation_power_irrational() {
1593 let base = rational_new(2, 1);
1594 let exponent = rational_new(1, 2);
1595 let failure = rational_operation(&base, NumericOperation::Power, &exponent).unwrap_err();
1596 assert_eq!(failure, NumericFailure::Irrational);
1597 }
1598
1599 #[test]
1600 fn rational_operation_power_exact() {
1601 let base = rational_new(4, 1);
1602 let exponent = rational_new(1, 2);
1603 let result = rational_operation(&base, NumericOperation::Power, &exponent).unwrap();
1604 assert_eq!(result, rational_new(2, 1));
1605 }
1606
1607 #[test]
1608 fn rational_power_exact_roots_of_rationals() {
1609 let power = |base: RationalInteger, exponent: RationalInteger| {
1610 rational_operation(&base, NumericOperation::Power, &exponent)
1611 };
1612 assert_eq!(
1613 power(rational_new(9, 4), rational_new(1, 2)).unwrap(),
1614 rational_new(3, 2)
1615 );
1616 assert_eq!(
1617 power(rational_new(9, 4), rational_new(-1, 2)).unwrap(),
1618 rational_new(2, 3)
1619 );
1620 assert_eq!(
1621 power(rational_new(8, 1), rational_new(2, 3)).unwrap(),
1622 rational_new(4, 1)
1623 );
1624 assert_eq!(
1625 power(rational_new(-8, 1), rational_new(1, 3)).unwrap(),
1626 rational_new(-2, 1)
1627 );
1628 assert_eq!(
1629 power(rational_new(-4, 1), rational_new(2, 1)).unwrap(),
1630 rational_new(16, 1)
1631 );
1632 assert_eq!(
1633 power(rational_new(1, 1), rational_new(49_595, 100_000)).unwrap(),
1634 rational_new(1, 1)
1635 );
1636 }
1637
1638 #[test]
1639 fn power_with_huge_integer_exponent_of_one_and_minus_one() {
1640 let even_huge =
1641 RationalInteger::from_bigint_strings("1000000000000000000000000000000", "1").unwrap();
1642 let odd_huge =
1643 RationalInteger::from_bigint_strings("1000000000000000000000000000001", "1").unwrap();
1644 assert_eq!(
1645 rational_operation(&rational_new(1, 1), NumericOperation::Power, &even_huge).unwrap(),
1646 rational_new(1, 1)
1647 );
1648 assert_eq!(
1649 rational_operation(&rational_new(-1, 1), NumericOperation::Power, &odd_huge).unwrap(),
1650 rational_new(-1, 1)
1651 );
1652 }
1653
1654 #[test]
1655 fn power_with_huge_root_degree_above_bit_length_is_irrational() {
1656 let degree = RationalInteger::from_bigint_strings("1", "1000000000000").unwrap();
1657 assert_eq!(
1658 rational_operation(&rational_new(2, 1), NumericOperation::Power, °ree).unwrap_err(),
1659 NumericFailure::Irrational
1660 );
1661 }
1662
1663 #[test]
1664 fn power_with_exponent_beyond_every_allocation_is_out_of_memory() {
1665 let exponent = BigInt::from_i128(1i128 << 70);
1666 assert_eq!(
1667 try_pow_integer(&rational_new(2, 1), &exponent).unwrap_err(),
1668 NumericFailure::OutOfMemory
1669 );
1670 assert_eq!(
1671 try_pow_integer(&rational_new(1, 1), &exponent).unwrap(),
1672 rational_new(1, 1)
1673 );
1674 }
1675
1676 #[test]
1677 fn rational_power_irrational_fails_fast_for_large_degree() {
1678 let failure = rational_operation(
1679 &rational_new(480, 1),
1680 NumericOperation::Power,
1681 &rational_new(49_595, 100_000),
1682 )
1683 .unwrap_err();
1684 assert_eq!(failure, NumericFailure::Irrational);
1685 let failure = rational_operation(
1686 &rational_new(2, 1),
1687 NumericOperation::Power,
1688 &rational_new(49_595, 100_000),
1689 )
1690 .unwrap_err();
1691 assert_eq!(failure, NumericFailure::Irrational);
1692 }
1693
1694 #[test]
1695 fn rational_power_negative_base_even_root_is_irrational() {
1696 let failure = rational_operation(
1697 &rational_new(-4, 1),
1698 NumericOperation::Power,
1699 &rational_new(1, 2),
1700 )
1701 .unwrap_err();
1702 assert_eq!(failure, NumericFailure::Irrational);
1703 }
1704
1705 #[test]
1706 fn rational_operation_add() {
1707 let left = rational_new(1, 3);
1708 let right = rational_new(1, 6);
1709 let sum = rational_operation(&left, NumericOperation::Add, &right).unwrap();
1710 assert_eq!(sum, rational_new(1, 2));
1711 }
1712
1713 #[test]
1714 fn rational_operation_with_fallback_add_exact_rational() {
1715 let left = rational_new(1, 3);
1716 let right = rational_new(1, 6);
1717 let sum = rational_operation_with_fallback(&left, NumericOperation::Add, &right).unwrap();
1718 assert_eq!(sum, rational_new(1, 2));
1719 }
1720
1721 #[test]
1722 fn rational_operation_with_fallback_power_sqrt_via_decimal() {
1723 let result = rational_operation_with_fallback(
1724 &rational_new(2, 1),
1725 NumericOperation::Power,
1726 &rational_new(1, 2),
1727 )
1728 .unwrap();
1729 let expected = rational_new(2, 1).try_to_decimal().unwrap().sqrt().unwrap();
1730 assert_eq!(
1731 result.try_to_decimal().unwrap().round_dp(27),
1732 expected.round_dp(27),
1733 );
1734 }
1735
1736 #[test]
1737 fn rational_operation_with_fallback_power_compound_day_fraction() {
1738 let base = decimal_to_rational(Decimal::from_str("1.04").expect("1.04")).expect("rational");
1739 let result = rational_operation_with_fallback(
1740 &base,
1741 NumericOperation::Power,
1742 &rational_new(28, 365),
1743 )
1744 .expect("1.04^(28/365) must use Decimal fallback");
1745 let decimal = result
1746 .try_to_decimal()
1747 .expect("fallback power must fit Decimal::MAX_SCALE");
1748 let expected = base
1749 .try_to_decimal()
1750 .expect("1.04")
1751 .checked_powd(Decimal::from_str("28").expect("28") / Decimal::from(365))
1752 .expect("checked_powd");
1753 let diff = (decimal - expected).abs();
1755 assert!(
1756 diff <= Decimal::new(1, 27),
1757 "1.04^(28/365) ≈ checked_powd within 1e-27, got {decimal} vs {expected} (diff {diff})"
1758 );
1759 }
1760
1761 #[test]
1762 fn rational_operation_with_fallback_power_cube_root_irrational() {
1763 let base = decimal_to_rational(Decimal::from_str("1.04").expect("1.04")).expect("rational");
1764 let result =
1765 rational_operation_with_fallback(&base, NumericOperation::Power, &rational_new(1, 3))
1766 .expect("1.04^(1/3) must use Decimal fallback");
1767 assert!(result.try_to_decimal().is_ok());
1768 }
1769
1770 #[test]
1771 fn rational_operation_with_fallback_power_negative_half() {
1772 let result = rational_operation_with_fallback(
1773 &rational_new(2, 1),
1774 NumericOperation::Power,
1775 &rational_new(-1, 2),
1776 )
1777 .expect("2^(-1/2) must use Decimal fallback");
1778 let actual = result.try_to_decimal().expect("fits Decimal");
1779 let expected = Decimal::ONE
1780 / rational_new(2, 1)
1781 .try_to_decimal()
1782 .expect("2")
1783 .sqrt()
1784 .expect("sqrt(2)");
1785 assert_eq!(actual.round_dp(26), expected.round_dp(26));
1786 }
1787
1788 #[test]
1789 fn rational_operation_with_fallback_power_negative_base_fraction_stays_irrational() {
1790 let err = rational_operation_with_fallback(
1791 &rational_new(-2, 1),
1792 NumericOperation::Power,
1793 &rational_new(1, 3),
1794 )
1795 .expect_err("negative base with non-integer exponent stays irrational on R");
1796 assert_eq!(err, NumericFailure::Irrational);
1797 }
1798
1799 #[test]
1800 fn rational_operation_exact_cube_root_unchanged() {
1801 let result = rational_operation(
1802 &rational_new(8, 1),
1803 NumericOperation::Power,
1804 &rational_new(1, 3),
1805 )
1806 .expect("8^(1/3) is exact in Q");
1807 assert_eq!(result, rational_new(2, 1));
1808 }
1809
1810 #[test]
1811 fn rational_modulo_is_floored_and_takes_divisor_sign() {
1812 assert_eq!(
1813 operate(
1814 &rational_new(-7, 1),
1815 NumericOperation::Modulo,
1816 &rational_new(3, 1)
1817 )
1818 .unwrap(),
1819 rational_new(2, 1)
1820 );
1821 assert_eq!(
1822 operate(
1823 &rational_new(7, 1),
1824 NumericOperation::Modulo,
1825 &rational_new(-3, 1)
1826 )
1827 .unwrap(),
1828 rational_new(-2, 1)
1829 );
1830 assert_eq!(
1831 operate(
1832 &rational_new(-7, 2),
1833 NumericOperation::Modulo,
1834 &rational_new(1, 1)
1835 )
1836 .unwrap(),
1837 rational_new(1, 2)
1838 );
1839 assert_eq!(
1840 operate(
1841 &rational_new(-7, 1),
1842 NumericOperation::Modulo,
1843 &rational_new(-3, 1)
1844 )
1845 .unwrap(),
1846 rational_new(-1, 1)
1847 );
1848 }
1849
1850 #[test]
1851 fn rational_abs_negates_negative_numerator() {
1852 let negative = rational_new(-172_800, 1);
1853 assert_eq!(rational_abs(&negative).unwrap(), rational_new(172_800, 1));
1854 }
1855
1856 #[test]
1857 fn try_to_decimal_string_rejects_magnitude_overflow() {
1858 let too_large =
1859 RationalInteger::from_bigint_strings("10000000000000000000000000000000", "1").unwrap();
1860 assert_eq!(
1861 too_large.try_to_decimal().unwrap_err(),
1862 NumericFailure::Overflow
1863 );
1864 assert_eq!(
1865 too_large.try_to_decimal_string().unwrap_err(),
1866 NumericFailure::Overflow,
1867 );
1868 }
1869
1870 #[test]
1871 fn decimal_max_times_decimal_max_stays_exact_without_decimal_fallback() {
1872 let max_decimal = Decimal::MAX.normalize();
1873 let left = decimal_to_rational(max_decimal).unwrap();
1874 let right = decimal_to_rational(max_decimal).unwrap();
1875 let product = rational_operation(&left, NumericOperation::Multiply, &right).unwrap();
1876 let expected = try_mul(&left, &right).unwrap();
1877 assert_eq!(product, expected);
1878 }
1879
1880 #[test]
1881 fn alloc_error_maps_to_out_of_memory() {
1882 assert_eq!(
1883 NumericFailure::from(crate::computation::bigint::AllocError),
1884 NumericFailure::OutOfMemory
1885 );
1886 }
1887
1888 #[test]
1889 fn small_path_basic_arithmetic() {
1890 let a = rational_new(3, 1);
1891 let b = rational_new(5, 1);
1892 assert_eq!(try_add(&a, &b).unwrap(), rational_new(8, 1));
1893 assert_eq!(try_sub(&a, &b).unwrap(), rational_new(-2, 1));
1894 assert_eq!(try_mul(&a, &b).unwrap(), rational_new(15, 1));
1895 assert_eq!(try_div(&a, &b).unwrap(), rational_new(3, 5));
1896 }
1897
1898 #[test]
1899 fn small_path_reduces() {
1900 let r = rational_new(6, 4);
1901 assert_eq!(r, rational_new(3, 2));
1902 }
1903
1904 #[test]
1905 fn small_path_try_to_decimal_integer() {
1906 let r = rational_new(42, 1);
1907 assert_eq!(r.try_to_decimal().unwrap(), Decimal::from(42));
1908 }
1909}