1use num_bigint::BigInt;
23use num_integer::Integer;
24use num_rational::BigRational;
25use num_traits::{One, Signed, Zero};
26use std::cmp::Ordering;
27use std::fmt;
28use std::hash::{Hash, Hasher};
29use std::ops::{
30 Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign,
31};
32
33#[derive(Clone, Debug)]
37pub enum FastRational {
38 Small {
40 num: i64,
42 den: i64,
44 },
45 Big(Box<BigRational>),
47}
48
49#[inline]
57fn gcd_i64(a: i64, b: i64) -> i64 {
58 let mut a = a.unsigned_abs();
60 let mut b = b.unsigned_abs();
61 if a == 0 {
62 return b as i64;
63 }
64 if b == 0 {
65 return a as i64;
66 }
67 let shift = (a | b).trailing_zeros();
69 a >>= a.trailing_zeros();
70 loop {
71 b >>= b.trailing_zeros();
72 if a > b {
73 std::mem::swap(&mut a, &mut b);
74 }
75 b -= a;
76 if b == 0 {
77 break;
78 }
79 }
80 (a << shift) as i64
81}
82
83impl FastRational {
88 #[inline]
95 pub fn new_small(num: i64, den: i64) -> Self {
96 debug_assert!(den != 0, "FastRational: denominator must not be zero");
97 if num == 0 {
98 return FastRational::Small { num: 0, den: 1 };
99 }
100 let (n, d) = if den < 0 {
101 match (num.checked_neg(), den.checked_neg()) {
104 (Some(nn), Some(dd)) => (nn, dd),
105 _ => {
106 let big = BigRational::new(BigInt::from(num), BigInt::from(den));
108 return FastRational::Big(Box::new(big));
109 }
110 }
111 } else {
112 (num, den)
113 };
114 let g = gcd_i64(n, d);
122 if g == 0 {
123 return FastRational::Small { num: 0, den: 1 };
124 }
125 FastRational::Small {
126 num: n / g,
127 den: d / g,
128 }
129 }
130
131 pub fn from_big(br: BigRational) -> Self {
133 let n_opt: Option<i64> = br.numer().try_into().ok();
135 let d_opt: Option<i64> = br.denom().try_into().ok();
136 match (n_opt, d_opt) {
137 (Some(n), Some(d)) if d != 0 => FastRational::new_small(n, d),
138 _ => FastRational::Big(Box::new(br)),
139 }
140 }
141
142 pub fn to_big_rational(&self) -> BigRational {
144 match self {
145 FastRational::Small { num, den } => {
146 BigRational::new(BigInt::from(*num), BigInt::from(*den))
147 }
148 FastRational::Big(b) => (**b).clone(),
149 }
150 }
151
152 #[inline]
154 pub fn to_f64(&self) -> f64 {
155 match self {
156 FastRational::Small { num, den } => *num as f64 / *den as f64,
157 FastRational::Big(b) => {
158 use num_traits::ToPrimitive;
159 b.numer().to_f64().unwrap_or(f64::NAN) / b.denom().to_f64().unwrap_or(f64::NAN)
160 }
161 }
162 }
163
164 pub fn recip(&self) -> Option<Self> {
166 match self {
167 FastRational::Small { num, den } => {
168 if *num == 0 {
169 None
170 } else {
171 Some(FastRational::new_small(*den, *num))
172 }
173 }
174 FastRational::Big(b) => {
175 if b.is_zero() {
176 None
177 } else {
178 Some(FastRational::from_big(b.recip()))
179 }
180 }
181 }
182 }
183
184 pub fn floor(&self) -> BigInt {
186 match self {
187 FastRational::Small { num, den } => {
188 if *den == 1 {
189 BigInt::from(*num)
190 } else {
191 let q = num.div_floor(den);
192 BigInt::from(q)
193 }
194 }
195 FastRational::Big(b) => crate::rational::floor(b),
196 }
197 }
198
199 pub fn ceil(&self) -> BigInt {
201 match self {
202 FastRational::Small { num, den } => {
203 if *den == 1 {
204 BigInt::from(*num)
205 } else {
206 let q = if *num >= 0 {
211 (*num + *den - 1) / *den
212 } else {
213 *num / *den
216 };
217 BigInt::from(q)
218 }
219 }
220 FastRational::Big(b) => crate::rational::ceil(b),
221 }
222 }
223
224 pub fn abs(&self) -> Self {
226 match self {
227 FastRational::Small { num, den } => {
228 match num.checked_abs() {
229 Some(n) => FastRational::Small { num: n, den: *den },
230 None => {
231 let big = BigRational::new(BigInt::from(*num).abs(), BigInt::from(*den));
233 FastRational::from_big(big)
234 }
235 }
236 }
237 FastRational::Big(b) => FastRational::from_big(b.abs()),
238 }
239 }
240
241 pub fn numer(&self) -> BigInt {
243 match self {
244 FastRational::Small { num, .. } => BigInt::from(*num),
245 FastRational::Big(b) => b.numer().clone(),
246 }
247 }
248
249 pub fn denom(&self) -> BigInt {
251 match self {
252 FastRational::Small { den, .. } => BigInt::from(*den),
253 FastRational::Big(b) => b.denom().clone(),
254 }
255 }
256
257 #[inline]
259 pub fn is_integer(&self) -> bool {
260 match self {
261 FastRational::Small { den, .. } => *den == 1,
262 FastRational::Big(b) => b.is_integer(),
263 }
264 }
265
266 pub fn from_big_ref(br: &BigRational) -> Self {
268 let n_opt: Option<i64> = br.numer().try_into().ok();
269 let d_opt: Option<i64> = br.denom().try_into().ok();
270 match (n_opt, d_opt) {
271 (Some(n), Some(d)) if d != 0 => FastRational::new_small(n, d),
272 _ => FastRational::Big(Box::new(br.clone())),
273 }
274 }
275
276 pub fn from_bigint(bi: &BigInt) -> Self {
278 let n_opt: Option<i64> = bi.try_into().ok();
279 match n_opt {
280 Some(n) => FastRational::Small { num: n, den: 1 },
281 None => FastRational::Big(Box::new(BigRational::from_integer(bi.clone()))),
282 }
283 }
284}
285
286#[inline]
292fn add_small(a_num: i64, a_den: i64, b_num: i64, b_den: i64) -> FastRational {
293 if let (Some(ad), Some(cb), Some(bd)) = (
295 a_num.checked_mul(b_den),
296 b_num.checked_mul(a_den),
297 a_den.checked_mul(b_den),
298 ) && let Some(num) = ad.checked_add(cb)
299 {
300 return FastRational::new_small(num, bd);
301 }
302 let a = BigRational::new(BigInt::from(a_num), BigInt::from(a_den));
304 let b = BigRational::new(BigInt::from(b_num), BigInt::from(b_den));
305 FastRational::from_big(a + b)
306}
307
308#[inline]
310fn sub_small(a_num: i64, a_den: i64, b_num: i64, b_den: i64) -> FastRational {
311 if let (Some(ad), Some(cb), Some(bd)) = (
313 a_num.checked_mul(b_den),
314 b_num.checked_mul(a_den),
315 a_den.checked_mul(b_den),
316 ) && let Some(num) = ad.checked_sub(cb)
317 {
318 return FastRational::new_small(num, bd);
319 }
320 let a = BigRational::new(BigInt::from(a_num), BigInt::from(a_den));
321 let b = BigRational::new(BigInt::from(b_num), BigInt::from(b_den));
322 FastRational::from_big(a - b)
323}
324
325#[inline]
327fn mul_small(a_num: i64, a_den: i64, b_num: i64, b_den: i64) -> FastRational {
328 let g1 = gcd_i64(a_num, b_den);
334 let g2 = gcd_i64(b_num, a_den);
335 let an = if g1 != 0 { a_num / g1 } else { a_num };
336 let bd = if g1 != 0 { b_den / g1 } else { b_den };
337 let bn = if g2 != 0 { b_num / g2 } else { b_num };
338 let ad = if g2 != 0 { a_den / g2 } else { a_den };
339
340 if let (Some(num), Some(den)) = (an.checked_mul(bn), ad.checked_mul(bd)) {
341 return FastRational::new_small(num, den);
342 }
343 let a = BigRational::new(BigInt::from(a_num), BigInt::from(a_den));
344 let b = BigRational::new(BigInt::from(b_num), BigInt::from(b_den));
345 FastRational::from_big(a * b)
346}
347
348#[inline]
351fn div_small(a_num: i64, a_den: i64, b_num: i64, b_den: i64) -> Option<FastRational> {
352 if b_num == 0 {
353 return None;
354 }
355 if let (Some(num), Some(den)) = (a_num.checked_mul(b_den), a_den.checked_mul(b_num)) {
359 Some(FastRational::new_small(num, den))
360 } else {
361 let a = BigRational::new(BigInt::from(a_num), BigInt::from(a_den));
362 let b = BigRational::new(BigInt::from(b_num), BigInt::from(b_den));
363 Some(FastRational::from_big(a / b))
364 }
365}
366
367impl From<i64> for FastRational {
372 #[inline]
373 fn from(n: i64) -> Self {
374 FastRational::Small { num: n, den: 1 }
375 }
376}
377
378impl From<(i64, i64)> for FastRational {
379 fn from((n, d): (i64, i64)) -> Self {
380 if d == 0 {
381 FastRational::Small { num: 0, den: 1 }
383 } else {
384 FastRational::new_small(n, d)
385 }
386 }
387}
388
389impl From<BigRational> for FastRational {
390 fn from(br: BigRational) -> Self {
391 FastRational::from_big(br)
392 }
393}
394
395impl From<BigInt> for FastRational {
396 fn from(bi: BigInt) -> Self {
397 FastRational::from_bigint(&bi)
398 }
399}
400
401impl From<&BigRational> for FastRational {
402 fn from(br: &BigRational) -> Self {
403 FastRational::from_big_ref(br)
404 }
405}
406
407impl Zero for FastRational {
412 #[inline]
413 fn zero() -> Self {
414 FastRational::Small { num: 0, den: 1 }
415 }
416
417 #[inline]
418 fn is_zero(&self) -> bool {
419 match self {
420 FastRational::Small { num, .. } => *num == 0,
421 FastRational::Big(b) => b.is_zero(),
422 }
423 }
424}
425
426impl One for FastRational {
427 #[inline]
428 fn one() -> Self {
429 FastRational::Small { num: 1, den: 1 }
430 }
431
432 #[inline]
433 fn is_one(&self) -> bool {
434 match self {
435 FastRational::Small { num, den } => *num == 1 && *den == 1,
436 FastRational::Big(b) => b.is_one(),
437 }
438 }
439}
440
441impl Signed for FastRational {
446 fn abs(&self) -> Self {
447 FastRational::abs(self)
448 }
449
450 fn abs_sub(&self, other: &Self) -> Self {
451 let diff = self - other;
452 if diff.is_positive() {
453 diff
454 } else {
455 FastRational::zero()
456 }
457 }
458
459 fn signum(&self) -> Self {
460 if self.is_zero() {
461 FastRational::zero()
462 } else if self.is_positive() {
463 FastRational::one()
464 } else {
465 FastRational::Small { num: -1, den: 1 }
466 }
467 }
468
469 fn is_positive(&self) -> bool {
470 match self {
471 FastRational::Small { num, .. } => *num > 0,
472 FastRational::Big(b) => b.is_positive(),
473 }
474 }
475
476 fn is_negative(&self) -> bool {
477 match self {
478 FastRational::Small { num, .. } => *num < 0,
479 FastRational::Big(b) => b.is_negative(),
480 }
481 }
482}
483
484impl num_traits::Num for FastRational {
485 type FromStrRadixErr = String;
486
487 fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
488 if let Some((num_str, den_str)) = str.split_once('/') {
490 let num = BigInt::from_str_radix(num_str.trim(), radix)
491 .map_err(|e| format!("invalid numerator: {}", e))?;
492 let den = BigInt::from_str_radix(den_str.trim(), radix)
493 .map_err(|e| format!("invalid denominator: {}", e))?;
494 if den.is_zero() {
495 return Err("denominator is zero".to_string());
496 }
497 Ok(FastRational::from_big(BigRational::new(num, den)))
498 } else {
499 let num = BigInt::from_str_radix(str.trim(), radix)
500 .map_err(|e| format!("invalid integer: {}", e))?;
501 Ok(FastRational::from_bigint(&num))
502 }
503 }
504}
505
506impl Neg for FastRational {
511 type Output = FastRational;
512
513 #[inline]
514 fn neg(self) -> Self::Output {
515 match self {
516 FastRational::Small { num, den } => match num.checked_neg() {
517 Some(n) => FastRational::Small { num: n, den },
518 None => {
519 let big = BigRational::new(-BigInt::from(num), BigInt::from(den));
520 FastRational::Big(Box::new(big))
521 }
522 },
523 FastRational::Big(b) => FastRational::from_big(-*b),
524 }
525 }
526}
527
528impl Neg for &FastRational {
529 type Output = FastRational;
530
531 #[inline]
532 fn neg(self) -> Self::Output {
533 match self {
534 FastRational::Small { num, den } => match num.checked_neg() {
535 Some(n) => FastRational::Small { num: n, den: *den },
536 None => {
537 let big = BigRational::new(-BigInt::from(*num), BigInt::from(*den));
538 FastRational::Big(Box::new(big))
539 }
540 },
541 FastRational::Big(b) => FastRational::from_big(-((**b).clone())),
542 }
543 }
544}
545
546impl Add for FastRational {
551 type Output = FastRational;
552
553 #[inline]
554 fn add(self, rhs: FastRational) -> Self::Output {
555 (&self).add(&rhs)
556 }
557}
558
559impl Add<&FastRational> for FastRational {
560 type Output = FastRational;
561
562 #[inline]
563 fn add(self, rhs: &FastRational) -> Self::Output {
564 (&self).add(rhs)
565 }
566}
567
568impl Add<FastRational> for &FastRational {
569 type Output = FastRational;
570
571 #[inline]
572 fn add(self, rhs: FastRational) -> Self::Output {
573 self.add(&rhs)
574 }
575}
576
577impl Add<&FastRational> for &FastRational {
578 type Output = FastRational;
579
580 #[inline]
581 fn add(self, rhs: &FastRational) -> Self::Output {
582 match (self, rhs) {
583 (
584 FastRational::Small { num: an, den: ad },
585 FastRational::Small { num: bn, den: bd },
586 ) => add_small(*an, *ad, *bn, *bd),
587 (a, b) => {
588 let big_a = a.to_big_rational();
589 let big_b = b.to_big_rational();
590 FastRational::from_big(big_a + big_b)
591 }
592 }
593 }
594}
595
596impl AddAssign for FastRational {
597 #[inline]
598 fn add_assign(&mut self, rhs: FastRational) {
599 *self = (&*self) + &rhs;
600 }
601}
602
603impl AddAssign<&FastRational> for FastRational {
604 #[inline]
605 fn add_assign(&mut self, rhs: &FastRational) {
606 *self = (&*self) + rhs;
607 }
608}
609
610impl Sub for FastRational {
615 type Output = FastRational;
616
617 #[inline]
618 fn sub(self, rhs: FastRational) -> Self::Output {
619 (&self).sub(&rhs)
620 }
621}
622
623impl Sub<&FastRational> for FastRational {
624 type Output = FastRational;
625
626 #[inline]
627 fn sub(self, rhs: &FastRational) -> Self::Output {
628 (&self).sub(rhs)
629 }
630}
631
632impl Sub<FastRational> for &FastRational {
633 type Output = FastRational;
634
635 #[inline]
636 fn sub(self, rhs: FastRational) -> Self::Output {
637 self.sub(&rhs)
638 }
639}
640
641impl Sub<&FastRational> for &FastRational {
642 type Output = FastRational;
643
644 #[inline]
645 fn sub(self, rhs: &FastRational) -> Self::Output {
646 match (self, rhs) {
647 (
648 FastRational::Small { num: an, den: ad },
649 FastRational::Small { num: bn, den: bd },
650 ) => sub_small(*an, *ad, *bn, *bd),
651 (a, b) => {
652 let big_a = a.to_big_rational();
653 let big_b = b.to_big_rational();
654 FastRational::from_big(big_a - big_b)
655 }
656 }
657 }
658}
659
660impl SubAssign for FastRational {
661 #[inline]
662 fn sub_assign(&mut self, rhs: FastRational) {
663 *self = (&*self) - &rhs;
664 }
665}
666
667impl SubAssign<&FastRational> for FastRational {
668 #[inline]
669 fn sub_assign(&mut self, rhs: &FastRational) {
670 *self = (&*self) - rhs;
671 }
672}
673
674impl Mul for FastRational {
679 type Output = FastRational;
680
681 #[inline]
682 fn mul(self, rhs: FastRational) -> Self::Output {
683 (&self).mul(&rhs)
684 }
685}
686
687impl Mul<&FastRational> for FastRational {
688 type Output = FastRational;
689
690 #[inline]
691 fn mul(self, rhs: &FastRational) -> Self::Output {
692 (&self).mul(rhs)
693 }
694}
695
696impl Mul<FastRational> for &FastRational {
697 type Output = FastRational;
698
699 #[inline]
700 fn mul(self, rhs: FastRational) -> Self::Output {
701 self.mul(&rhs)
702 }
703}
704
705impl Mul<&FastRational> for &FastRational {
706 type Output = FastRational;
707
708 #[inline]
709 fn mul(self, rhs: &FastRational) -> Self::Output {
710 match (self, rhs) {
711 (
712 FastRational::Small { num: an, den: ad },
713 FastRational::Small { num: bn, den: bd },
714 ) => mul_small(*an, *ad, *bn, *bd),
715 (a, b) => {
716 let big_a = a.to_big_rational();
717 let big_b = b.to_big_rational();
718 FastRational::from_big(big_a * big_b)
719 }
720 }
721 }
722}
723
724impl MulAssign for FastRational {
725 #[inline]
726 fn mul_assign(&mut self, rhs: FastRational) {
727 *self = (&*self) * &rhs;
728 }
729}
730
731impl MulAssign<&FastRational> for FastRational {
732 #[inline]
733 fn mul_assign(&mut self, rhs: &FastRational) {
734 *self = (&*self) * rhs;
735 }
736}
737
738impl Div for FastRational {
743 type Output = FastRational;
744
745 #[inline]
751 fn div(self, rhs: FastRational) -> Self::Output {
752 (&self).div(&rhs)
753 }
754}
755
756impl Div<&FastRational> for FastRational {
757 type Output = FastRational;
758
759 #[inline]
760 fn div(self, rhs: &FastRational) -> Self::Output {
761 (&self).div(rhs)
762 }
763}
764
765impl Div<FastRational> for &FastRational {
766 type Output = FastRational;
767
768 #[inline]
769 fn div(self, rhs: FastRational) -> Self::Output {
770 self.div(&rhs)
771 }
772}
773
774impl Div<&FastRational> for &FastRational {
775 type Output = FastRational;
776
777 #[inline]
778 fn div(self, rhs: &FastRational) -> Self::Output {
779 match (self, rhs) {
780 (
781 FastRational::Small { num: an, den: ad },
782 FastRational::Small { num: bn, den: bd },
783 ) => match div_small(*an, *ad, *bn, *bd) {
784 Some(r) => r,
785 None => panic!("FastRational: division by zero"),
791 },
792 (a, b) => {
793 if b.is_zero() {
794 panic!("FastRational: division by zero");
795 }
796 let big_a = a.to_big_rational();
797 let big_b = b.to_big_rational();
798 FastRational::from_big(big_a / big_b)
799 }
800 }
801 }
802}
803
804impl DivAssign for FastRational {
805 #[inline]
806 fn div_assign(&mut self, rhs: FastRational) {
807 *self = (&*self) / &rhs;
808 }
809}
810
811impl DivAssign<&FastRational> for FastRational {
812 #[inline]
813 fn div_assign(&mut self, rhs: &FastRational) {
814 *self = (&*self) / rhs;
815 }
816}
817
818impl Rem for FastRational {
823 type Output = FastRational;
824
825 #[inline]
829 fn rem(self, rhs: FastRational) -> Self::Output {
830 (&self).rem(&rhs)
831 }
832}
833
834impl Rem<&FastRational> for &FastRational {
835 type Output = FastRational;
836
837 #[inline]
838 fn rem(self, rhs: &FastRational) -> Self::Output {
839 if rhs.is_zero() {
840 return FastRational::zero();
841 }
842 let quotient = self / rhs;
843 let floor_q = FastRational::from_integer(quotient.floor());
844 self - &(rhs * &floor_q)
845 }
846}
847
848impl RemAssign for FastRational {
849 #[inline]
850 fn rem_assign(&mut self, rhs: FastRational) {
851 *self = (&*self).rem(&rhs);
852 }
853}
854
855impl PartialEq for FastRational {
860 fn eq(&self, other: &Self) -> bool {
861 match (self, other) {
862 (
863 FastRational::Small { num: an, den: ad },
864 FastRational::Small { num: bn, den: bd },
865 ) => {
866 an == bn && ad == bd
868 }
869 (a, b) => {
870 a.to_big_rational() == b.to_big_rational()
872 }
873 }
874 }
875}
876
877impl Eq for FastRational {}
878
879impl PartialOrd for FastRational {
884 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
885 Some(self.cmp(other))
886 }
887}
888
889impl Ord for FastRational {
890 fn cmp(&self, other: &Self) -> Ordering {
891 match (self, other) {
892 (
893 FastRational::Small { num: an, den: ad },
894 FastRational::Small { num: bn, den: bd },
895 ) => {
896 if let (Some(lhs), Some(rhs)) = (an.checked_mul(*bd), bn.checked_mul(*ad)) {
900 return lhs.cmp(&rhs);
901 }
902 let big_a = BigRational::new(BigInt::from(*an), BigInt::from(*ad));
904 let big_b = BigRational::new(BigInt::from(*bn), BigInt::from(*bd));
905 big_a.cmp(&big_b)
906 }
907 (a, b) => {
908 let big_a = a.to_big_rational();
909 let big_b = b.to_big_rational();
910 big_a.cmp(&big_b)
911 }
912 }
913 }
914}
915
916impl Hash for FastRational {
921 fn hash<H: Hasher>(&self, state: &mut H) {
922 match self {
926 FastRational::Small { num, den } => {
927 num.hash(state);
928 den.hash(state);
929 }
930 FastRational::Big(b) => {
931 let reduced = b.reduced();
933 let n_opt: Option<i64> = reduced.numer().try_into().ok();
934 let d_opt: Option<i64> = reduced.denom().try_into().ok();
935 match (n_opt, d_opt) {
936 (Some(n), Some(d)) if d > 0 => {
937 n.hash(state);
938 d.hash(state);
939 }
940 (Some(n), Some(d)) if d < 0 => {
941 (-n).hash(state);
943 (-d).hash(state);
944 }
945 _ => {
946 let (n, d) = if reduced.denom().is_negative() {
949 (-reduced.numer(), -reduced.denom())
950 } else {
951 (reduced.numer().clone(), reduced.denom().clone())
952 };
953 n.hash(state);
954 d.hash(state);
955 }
956 }
957 }
958 }
959 }
960}
961
962impl fmt::Display for FastRational {
967 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
968 match self {
969 FastRational::Small { num, den } => {
970 if *den == 1 {
971 write!(f, "{}", num)
972 } else {
973 write!(f, "{}/{}", num, den)
974 }
975 }
976 FastRational::Big(b) => write!(f, "{}", b),
977 }
978 }
979}
980
981impl FastRational {
986 #[inline]
988 pub fn from_integer(n: BigInt) -> Self {
989 FastRational::from_bigint(&n)
990 }
991}
992
993impl FastRational {
998 #[inline]
1000 pub fn max(self, other: Self) -> Self {
1001 if self >= other { self } else { other }
1002 }
1003
1004 #[inline]
1006 pub fn min(self, other: Self) -> Self {
1007 if self <= other { self } else { other }
1008 }
1009}
1010
1011#[cfg(test)]
1016mod tests {
1017 use super::*;
1018 use std::collections::hash_map::DefaultHasher;
1019
1020 fn small(n: i64, d: i64) -> FastRational {
1021 FastRational::new_small(n, d)
1022 }
1023
1024 fn fr(n: i64) -> FastRational {
1025 FastRational::from(n)
1026 }
1027
1028 fn hash_of(val: &FastRational) -> u64 {
1029 let mut h = DefaultHasher::new();
1030 val.hash(&mut h);
1031 h.finish()
1032 }
1033
1034 #[test]
1037 fn test_new_small_normalization() {
1038 let r = small(2, 4);
1040 assert_eq!(r, small(1, 2));
1041 }
1042
1043 #[test]
1044 fn test_new_small_negative_den() {
1045 let r = small(3, -5);
1047 assert_eq!(r, small(-3, 5));
1048 }
1049
1050 #[test]
1051 fn test_new_small_zero() {
1052 let r = small(0, 42);
1053 assert_eq!(r, FastRational::zero());
1054 }
1055
1056 #[test]
1057 fn test_from_i64() {
1058 let r = FastRational::from(7i64);
1059 assert_eq!(r, small(7, 1));
1060 }
1061
1062 #[test]
1063 fn test_from_tuple() {
1064 let r = FastRational::from((6i64, 4i64));
1065 assert_eq!(r, small(3, 2));
1066 }
1067
1068 #[test]
1069 fn test_from_bigrational() {
1070 let br = BigRational::new(BigInt::from(10), BigInt::from(4));
1071 let r = FastRational::from(br);
1072 assert_eq!(r, small(5, 2));
1073 }
1074
1075 #[test]
1078 fn test_add() {
1079 let a = small(1, 2);
1081 let b = small(1, 3);
1082 assert_eq!(&a + &b, small(5, 6));
1083 }
1084
1085 #[test]
1086 fn test_sub() {
1087 let a = small(3, 4);
1089 let b = small(1, 4);
1090 assert_eq!(&a - &b, small(1, 2));
1091 }
1092
1093 #[test]
1094 fn test_mul() {
1095 let a = small(2, 3);
1097 let b = small(3, 4);
1098 assert_eq!(&a * &b, small(1, 2));
1099 }
1100
1101 #[test]
1102 fn test_div() {
1103 let a = small(2, 3);
1105 let b = small(4, 5);
1106 assert_eq!(&a / &b, small(5, 6));
1107 }
1108
1109 #[test]
1110 fn test_neg() {
1111 let r = small(3, 5);
1112 assert_eq!(-r, small(-3, 5));
1113 }
1114
1115 #[test]
1116 fn test_add_assign() {
1117 let mut a = small(1, 2);
1118 a += small(1, 3);
1119 assert_eq!(a, small(5, 6));
1120 }
1121
1122 #[test]
1123 fn test_mul_assign() {
1124 let mut a = small(2, 3);
1125 a *= small(3, 4);
1126 assert_eq!(a, small(1, 2));
1127 }
1128
1129 #[test]
1132 fn test_overflow_to_big() {
1133 let big_val = i64::MAX;
1134 let a = fr(big_val);
1135 let b = fr(big_val);
1136 let result = &a + &b;
1137 let expected = BigRational::from_integer(BigInt::from(big_val))
1139 + BigRational::from_integer(BigInt::from(big_val));
1140 assert_eq!(result.to_big_rational(), expected);
1141 }
1142
1143 #[test]
1146 fn test_ord() {
1147 assert!(small(1, 3) < small(1, 2));
1148 assert!(small(-1, 2) < small(1, 2));
1149 assert!(small(0, 1) == FastRational::zero());
1150 }
1151
1152 #[test]
1153 fn test_eq_cross_representation() {
1154 let s = small(1, 2);
1156 let b = FastRational::Big(Box::new(BigRational::new(BigInt::from(1), BigInt::from(2))));
1157 assert_eq!(s, b);
1158 }
1159
1160 #[test]
1163 fn test_hash_consistency() {
1164 let s = small(1, 2);
1165 let b = FastRational::Big(Box::new(BigRational::new(BigInt::from(1), BigInt::from(2))));
1166 assert_eq!(hash_of(&s), hash_of(&b));
1167 }
1168
1169 #[test]
1172 fn test_zero_one() {
1173 assert!(FastRational::zero().is_zero());
1174 assert!(FastRational::one().is_one());
1175 assert!(!FastRational::zero().is_one());
1176 assert!(!FastRational::one().is_zero());
1177 }
1178
1179 #[test]
1180 fn test_signed() {
1181 assert!(small(3, 5).is_positive());
1182 assert!(small(-3, 5).is_negative());
1183 assert!(!FastRational::zero().is_positive());
1184 assert!(!FastRational::zero().is_negative());
1185 }
1186
1187 #[test]
1188 fn test_abs() {
1189 assert_eq!(small(-3, 5).abs(), small(3, 5));
1190 assert_eq!(small(3, 5).abs(), small(3, 5));
1191 }
1192
1193 #[test]
1196 fn test_recip() {
1197 assert_eq!(small(3, 5).recip(), Some(small(5, 3)));
1198 assert_eq!(FastRational::zero().recip(), None);
1199 }
1200
1201 #[test]
1202 fn test_floor_ceil() {
1203 let r = small(7, 3);
1205 assert_eq!(r.floor(), BigInt::from(2));
1206 assert_eq!(r.ceil(), BigInt::from(3));
1207
1208 let r = small(-7, 3);
1210 assert_eq!(r.floor(), BigInt::from(-3));
1211 assert_eq!(r.ceil(), BigInt::from(-2));
1212 }
1213
1214 #[test]
1215 fn test_to_f64() {
1216 let r = small(1, 2);
1217 assert!((r.to_f64() - 0.5).abs() < 1e-10);
1218 }
1219
1220 #[test]
1221 fn test_to_big_rational_roundtrip() {
1222 let r = small(7, 13);
1223 let big = r.to_big_rational();
1224 let back = FastRational::from_big(big);
1225 assert_eq!(r, back);
1226 }
1227
1228 #[test]
1229 fn test_numer_denom() {
1230 let r = small(3, 7);
1231 assert_eq!(r.numer(), BigInt::from(3));
1232 assert_eq!(r.denom(), BigInt::from(7));
1233 }
1234
1235 #[test]
1236 fn test_is_integer() {
1237 assert!(fr(5).is_integer());
1238 assert!(!small(5, 3).is_integer());
1239 }
1240
1241 #[test]
1242 fn test_display() {
1243 assert_eq!(format!("{}", fr(5)), "5");
1244 assert_eq!(format!("{}", small(3, 7)), "3/7");
1245 assert_eq!(format!("{}", small(-1, 2)), "-1/2");
1246 }
1247
1248 #[test]
1249 fn test_max_min() {
1250 let a = small(1, 3);
1251 let b = small(1, 2);
1252 assert_eq!(a.clone().max(b.clone()), b);
1253 assert_eq!(a.clone().min(b.clone()), a);
1254 }
1255
1256 #[test]
1262 fn test_new_small_i64_min_odd_denominator_stays_irreducible() {
1263 let r = FastRational::new_small(i64::MIN, 7);
1266 assert_eq!(
1267 r,
1268 FastRational::Small {
1269 num: i64::MIN,
1270 den: 7
1271 }
1272 );
1273 let expected = BigRational::new(BigInt::from(i64::MIN), BigInt::from(7));
1275 assert_eq!(r.to_big_rational(), expected);
1276 }
1277
1278 #[test]
1279 fn test_new_small_i64_min_even_denominator_reduces_correctly() {
1280 let r = FastRational::new_small(i64::MIN, 2);
1283 assert_eq!(
1284 r,
1285 FastRational::Small {
1286 num: i64::MIN / 2,
1287 den: 1
1288 }
1289 );
1290 let expected = BigRational::new(BigInt::from(i64::MIN), BigInt::from(2));
1291 assert_eq!(r.to_big_rational(), expected);
1292 }
1293
1294 #[test]
1295 fn test_mul_small_i64_min_by_small_fraction() {
1296 let a = FastRational::Small {
1302 num: i64::MIN,
1303 den: 1,
1304 };
1305 let b = FastRational::Small { num: 1, den: 7 };
1306 let result = &a * &b;
1307
1308 let expected = BigRational::new(BigInt::from(i64::MIN), BigInt::from(7));
1309 assert_eq!(
1310 result.to_big_rational(),
1311 expected,
1312 "MIN * (1/7) must equal MIN/7 exactly, not truncate to an integer"
1313 );
1314 assert!(!result.is_integer());
1316 }
1317
1318 #[test]
1319 fn test_mul_small_i64_min_eq_hash_invariant_preserved() {
1320 let a = FastRational::Small {
1324 num: i64::MIN,
1325 den: 1,
1326 };
1327 let b = FastRational::Small { num: 1, den: 7 };
1328 let small_result = &a * &b;
1329
1330 let big_a = BigRational::from_integer(BigInt::from(i64::MIN));
1331 let big_b = BigRational::new(BigInt::one(), BigInt::from(7));
1332 let big_result = FastRational::from_big(big_a * big_b);
1333
1334 assert_eq!(small_result, big_result);
1335 assert_eq!(hash_of(&small_result), hash_of(&big_result));
1336 }
1337
1338 #[test]
1339 fn test_from_str_radix() {
1340 use num_traits::Num;
1341 let r = FastRational::from_str_radix("3/7", 10);
1342 assert!(r.is_ok());
1343 assert_eq!(r.ok(), Some(small(3, 7)));
1344
1345 let r2 = FastRational::from_str_radix("42", 10);
1346 assert!(r2.is_ok());
1347 assert_eq!(r2.ok(), Some(fr(42)));
1348 }
1349}