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 = num.div_ceil(den);
215 BigInt::from(q)
216 }
217 }
218 FastRational::Big(b) => crate::rational::ceil(b),
219 }
220 }
221
222 pub fn abs(&self) -> Self {
224 match self {
225 FastRational::Small { num, den } => {
226 match num.checked_abs() {
227 Some(n) => FastRational::Small { num: n, den: *den },
228 None => {
229 let big = BigRational::new(BigInt::from(*num).abs(), BigInt::from(*den));
231 FastRational::from_big(big)
232 }
233 }
234 }
235 FastRational::Big(b) => FastRational::from_big(b.abs()),
236 }
237 }
238
239 pub fn numer(&self) -> BigInt {
241 match self {
242 FastRational::Small { num, .. } => BigInt::from(*num),
243 FastRational::Big(b) => b.numer().clone(),
244 }
245 }
246
247 pub fn denom(&self) -> BigInt {
249 match self {
250 FastRational::Small { den, .. } => BigInt::from(*den),
251 FastRational::Big(b) => b.denom().clone(),
252 }
253 }
254
255 #[inline]
257 pub fn is_integer(&self) -> bool {
258 match self {
259 FastRational::Small { den, .. } => *den == 1,
260 FastRational::Big(b) => b.is_integer(),
261 }
262 }
263
264 pub fn from_big_ref(br: &BigRational) -> Self {
266 let n_opt: Option<i64> = br.numer().try_into().ok();
267 let d_opt: Option<i64> = br.denom().try_into().ok();
268 match (n_opt, d_opt) {
269 (Some(n), Some(d)) if d != 0 => FastRational::new_small(n, d),
270 _ => FastRational::Big(Box::new(br.clone())),
271 }
272 }
273
274 pub fn from_bigint(bi: &BigInt) -> Self {
276 let n_opt: Option<i64> = bi.try_into().ok();
277 match n_opt {
278 Some(n) => FastRational::Small { num: n, den: 1 },
279 None => FastRational::Big(Box::new(BigRational::from_integer(bi.clone()))),
280 }
281 }
282}
283
284#[inline]
290fn add_small(a_num: i64, a_den: i64, b_num: i64, b_den: i64) -> FastRational {
291 if let (Some(ad), Some(cb), Some(bd)) = (
293 a_num.checked_mul(b_den),
294 b_num.checked_mul(a_den),
295 a_den.checked_mul(b_den),
296 ) && let Some(num) = ad.checked_add(cb)
297 {
298 return FastRational::new_small(num, bd);
299 }
300 let a = BigRational::new(BigInt::from(a_num), BigInt::from(a_den));
302 let b = BigRational::new(BigInt::from(b_num), BigInt::from(b_den));
303 FastRational::from_big(a + b)
304}
305
306#[inline]
308fn sub_small(a_num: i64, a_den: i64, b_num: i64, b_den: i64) -> FastRational {
309 if let (Some(ad), Some(cb), Some(bd)) = (
311 a_num.checked_mul(b_den),
312 b_num.checked_mul(a_den),
313 a_den.checked_mul(b_den),
314 ) && let Some(num) = ad.checked_sub(cb)
315 {
316 return FastRational::new_small(num, bd);
317 }
318 let a = BigRational::new(BigInt::from(a_num), BigInt::from(a_den));
319 let b = BigRational::new(BigInt::from(b_num), BigInt::from(b_den));
320 FastRational::from_big(a - b)
321}
322
323#[inline]
325fn mul_small(a_num: i64, a_den: i64, b_num: i64, b_den: i64) -> FastRational {
326 let g1 = gcd_i64(a_num, b_den);
332 let g2 = gcd_i64(b_num, a_den);
333 let an = if g1 != 0 { a_num / g1 } else { a_num };
334 let bd = if g1 != 0 { b_den / g1 } else { b_den };
335 let bn = if g2 != 0 { b_num / g2 } else { b_num };
336 let ad = if g2 != 0 { a_den / g2 } else { a_den };
337
338 if let (Some(num), Some(den)) = (an.checked_mul(bn), ad.checked_mul(bd)) {
339 return FastRational::new_small(num, den);
340 }
341 let a = BigRational::new(BigInt::from(a_num), BigInt::from(a_den));
342 let b = BigRational::new(BigInt::from(b_num), BigInt::from(b_den));
343 FastRational::from_big(a * b)
344}
345
346#[inline]
349fn div_small(a_num: i64, a_den: i64, b_num: i64, b_den: i64) -> Option<FastRational> {
350 if b_num == 0 {
351 return None;
352 }
353 if let (Some(num), Some(den)) = (a_num.checked_mul(b_den), a_den.checked_mul(b_num)) {
357 Some(FastRational::new_small(num, den))
358 } else {
359 let a = BigRational::new(BigInt::from(a_num), BigInt::from(a_den));
360 let b = BigRational::new(BigInt::from(b_num), BigInt::from(b_den));
361 Some(FastRational::from_big(a / b))
362 }
363}
364
365impl From<i64> for FastRational {
370 #[inline]
371 fn from(n: i64) -> Self {
372 FastRational::Small { num: n, den: 1 }
373 }
374}
375
376impl From<(i64, i64)> for FastRational {
377 fn from((n, d): (i64, i64)) -> Self {
378 if d == 0 {
379 FastRational::Small { num: 0, den: 1 }
381 } else {
382 FastRational::new_small(n, d)
383 }
384 }
385}
386
387impl From<BigRational> for FastRational {
388 fn from(br: BigRational) -> Self {
389 FastRational::from_big(br)
390 }
391}
392
393impl From<BigInt> for FastRational {
394 fn from(bi: BigInt) -> Self {
395 FastRational::from_bigint(&bi)
396 }
397}
398
399impl From<&BigRational> for FastRational {
400 fn from(br: &BigRational) -> Self {
401 FastRational::from_big_ref(br)
402 }
403}
404
405impl Zero for FastRational {
410 #[inline]
411 fn zero() -> Self {
412 FastRational::Small { num: 0, den: 1 }
413 }
414
415 #[inline]
416 fn is_zero(&self) -> bool {
417 match self {
418 FastRational::Small { num, .. } => *num == 0,
419 FastRational::Big(b) => b.is_zero(),
420 }
421 }
422}
423
424impl One for FastRational {
425 #[inline]
426 fn one() -> Self {
427 FastRational::Small { num: 1, den: 1 }
428 }
429
430 #[inline]
431 fn is_one(&self) -> bool {
432 match self {
433 FastRational::Small { num, den } => *num == 1 && *den == 1,
434 FastRational::Big(b) => b.is_one(),
435 }
436 }
437}
438
439impl Signed for FastRational {
444 fn abs(&self) -> Self {
445 FastRational::abs(self)
446 }
447
448 fn abs_sub(&self, other: &Self) -> Self {
449 let diff = self - other;
450 if diff.is_positive() {
451 diff
452 } else {
453 FastRational::zero()
454 }
455 }
456
457 fn signum(&self) -> Self {
458 if self.is_zero() {
459 FastRational::zero()
460 } else if self.is_positive() {
461 FastRational::one()
462 } else {
463 FastRational::Small { num: -1, den: 1 }
464 }
465 }
466
467 fn is_positive(&self) -> bool {
468 match self {
469 FastRational::Small { num, .. } => *num > 0,
470 FastRational::Big(b) => b.is_positive(),
471 }
472 }
473
474 fn is_negative(&self) -> bool {
475 match self {
476 FastRational::Small { num, .. } => *num < 0,
477 FastRational::Big(b) => b.is_negative(),
478 }
479 }
480}
481
482impl num_traits::Num for FastRational {
483 type FromStrRadixErr = String;
484
485 fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
486 if let Some((num_str, den_str)) = str.split_once('/') {
488 let num = BigInt::from_str_radix(num_str.trim(), radix)
489 .map_err(|e| format!("invalid numerator: {}", e))?;
490 let den = BigInt::from_str_radix(den_str.trim(), radix)
491 .map_err(|e| format!("invalid denominator: {}", e))?;
492 if den.is_zero() {
493 return Err("denominator is zero".to_string());
494 }
495 Ok(FastRational::from_big(BigRational::new(num, den)))
496 } else {
497 let num = BigInt::from_str_radix(str.trim(), radix)
498 .map_err(|e| format!("invalid integer: {}", e))?;
499 Ok(FastRational::from_bigint(&num))
500 }
501 }
502}
503
504impl Neg for FastRational {
509 type Output = FastRational;
510
511 #[inline]
512 fn neg(self) -> Self::Output {
513 match self {
514 FastRational::Small { num, den } => match num.checked_neg() {
515 Some(n) => FastRational::Small { num: n, den },
516 None => {
517 let big = BigRational::new(-BigInt::from(num), BigInt::from(den));
518 FastRational::Big(Box::new(big))
519 }
520 },
521 FastRational::Big(b) => FastRational::from_big(-*b),
522 }
523 }
524}
525
526impl Neg for &FastRational {
527 type Output = FastRational;
528
529 #[inline]
530 fn neg(self) -> Self::Output {
531 match self {
532 FastRational::Small { num, den } => match num.checked_neg() {
533 Some(n) => FastRational::Small { num: n, den: *den },
534 None => {
535 let big = BigRational::new(-BigInt::from(*num), BigInt::from(*den));
536 FastRational::Big(Box::new(big))
537 }
538 },
539 FastRational::Big(b) => FastRational::from_big(-((**b).clone())),
540 }
541 }
542}
543
544impl Add for FastRational {
549 type Output = FastRational;
550
551 #[inline]
552 fn add(self, rhs: FastRational) -> Self::Output {
553 (&self).add(&rhs)
554 }
555}
556
557impl Add<&FastRational> for FastRational {
558 type Output = FastRational;
559
560 #[inline]
561 fn add(self, rhs: &FastRational) -> Self::Output {
562 (&self).add(rhs)
563 }
564}
565
566impl Add<FastRational> for &FastRational {
567 type Output = FastRational;
568
569 #[inline]
570 fn add(self, rhs: FastRational) -> Self::Output {
571 self.add(&rhs)
572 }
573}
574
575impl Add<&FastRational> for &FastRational {
576 type Output = FastRational;
577
578 #[inline]
579 fn add(self, rhs: &FastRational) -> Self::Output {
580 match (self, rhs) {
581 (
582 FastRational::Small { num: an, den: ad },
583 FastRational::Small { num: bn, den: bd },
584 ) => add_small(*an, *ad, *bn, *bd),
585 (a, b) => {
586 let big_a = a.to_big_rational();
587 let big_b = b.to_big_rational();
588 FastRational::from_big(big_a + big_b)
589 }
590 }
591 }
592}
593
594impl AddAssign for FastRational {
595 #[inline]
596 fn add_assign(&mut self, rhs: FastRational) {
597 *self = (&*self) + &rhs;
598 }
599}
600
601impl AddAssign<&FastRational> for FastRational {
602 #[inline]
603 fn add_assign(&mut self, rhs: &FastRational) {
604 *self = (&*self) + rhs;
605 }
606}
607
608impl Sub for FastRational {
613 type Output = FastRational;
614
615 #[inline]
616 fn sub(self, rhs: FastRational) -> Self::Output {
617 (&self).sub(&rhs)
618 }
619}
620
621impl Sub<&FastRational> for FastRational {
622 type Output = FastRational;
623
624 #[inline]
625 fn sub(self, rhs: &FastRational) -> Self::Output {
626 (&self).sub(rhs)
627 }
628}
629
630impl Sub<FastRational> for &FastRational {
631 type Output = FastRational;
632
633 #[inline]
634 fn sub(self, rhs: FastRational) -> Self::Output {
635 self.sub(&rhs)
636 }
637}
638
639impl Sub<&FastRational> for &FastRational {
640 type Output = FastRational;
641
642 #[inline]
643 fn sub(self, rhs: &FastRational) -> Self::Output {
644 match (self, rhs) {
645 (
646 FastRational::Small { num: an, den: ad },
647 FastRational::Small { num: bn, den: bd },
648 ) => sub_small(*an, *ad, *bn, *bd),
649 (a, b) => {
650 let big_a = a.to_big_rational();
651 let big_b = b.to_big_rational();
652 FastRational::from_big(big_a - big_b)
653 }
654 }
655 }
656}
657
658impl SubAssign for FastRational {
659 #[inline]
660 fn sub_assign(&mut self, rhs: FastRational) {
661 *self = (&*self) - &rhs;
662 }
663}
664
665impl SubAssign<&FastRational> for FastRational {
666 #[inline]
667 fn sub_assign(&mut self, rhs: &FastRational) {
668 *self = (&*self) - rhs;
669 }
670}
671
672impl Mul for FastRational {
677 type Output = FastRational;
678
679 #[inline]
680 fn mul(self, rhs: FastRational) -> Self::Output {
681 (&self).mul(&rhs)
682 }
683}
684
685impl Mul<&FastRational> for FastRational {
686 type Output = FastRational;
687
688 #[inline]
689 fn mul(self, rhs: &FastRational) -> Self::Output {
690 (&self).mul(rhs)
691 }
692}
693
694impl Mul<FastRational> for &FastRational {
695 type Output = FastRational;
696
697 #[inline]
698 fn mul(self, rhs: FastRational) -> Self::Output {
699 self.mul(&rhs)
700 }
701}
702
703impl Mul<&FastRational> for &FastRational {
704 type Output = FastRational;
705
706 #[inline]
707 fn mul(self, rhs: &FastRational) -> Self::Output {
708 match (self, rhs) {
709 (
710 FastRational::Small { num: an, den: ad },
711 FastRational::Small { num: bn, den: bd },
712 ) => mul_small(*an, *ad, *bn, *bd),
713 (a, b) => {
714 let big_a = a.to_big_rational();
715 let big_b = b.to_big_rational();
716 FastRational::from_big(big_a * big_b)
717 }
718 }
719 }
720}
721
722impl MulAssign for FastRational {
723 #[inline]
724 fn mul_assign(&mut self, rhs: FastRational) {
725 *self = (&*self) * &rhs;
726 }
727}
728
729impl MulAssign<&FastRational> for FastRational {
730 #[inline]
731 fn mul_assign(&mut self, rhs: &FastRational) {
732 *self = (&*self) * rhs;
733 }
734}
735
736impl Div for FastRational {
741 type Output = FastRational;
742
743 #[inline]
749 fn div(self, rhs: FastRational) -> Self::Output {
750 (&self).div(&rhs)
751 }
752}
753
754impl Div<&FastRational> for FastRational {
755 type Output = FastRational;
756
757 #[inline]
758 fn div(self, rhs: &FastRational) -> Self::Output {
759 (&self).div(rhs)
760 }
761}
762
763impl Div<FastRational> for &FastRational {
764 type Output = FastRational;
765
766 #[inline]
767 fn div(self, rhs: FastRational) -> Self::Output {
768 self.div(&rhs)
769 }
770}
771
772impl Div<&FastRational> for &FastRational {
773 type Output = FastRational;
774
775 #[inline]
776 fn div(self, rhs: &FastRational) -> Self::Output {
777 match (self, rhs) {
778 (
779 FastRational::Small { num: an, den: ad },
780 FastRational::Small { num: bn, den: bd },
781 ) => match div_small(*an, *ad, *bn, *bd) {
782 Some(r) => r,
783 None => panic!("FastRational: division by zero"),
789 },
790 (a, b) => {
791 if b.is_zero() {
792 panic!("FastRational: division by zero");
793 }
794 let big_a = a.to_big_rational();
795 let big_b = b.to_big_rational();
796 FastRational::from_big(big_a / big_b)
797 }
798 }
799 }
800}
801
802impl DivAssign for FastRational {
803 #[inline]
804 fn div_assign(&mut self, rhs: FastRational) {
805 *self = (&*self) / &rhs;
806 }
807}
808
809impl DivAssign<&FastRational> for FastRational {
810 #[inline]
811 fn div_assign(&mut self, rhs: &FastRational) {
812 *self = (&*self) / rhs;
813 }
814}
815
816impl Rem for FastRational {
821 type Output = FastRational;
822
823 #[inline]
827 fn rem(self, rhs: FastRational) -> Self::Output {
828 (&self).rem(&rhs)
829 }
830}
831
832impl Rem<&FastRational> for &FastRational {
833 type Output = FastRational;
834
835 #[inline]
836 fn rem(self, rhs: &FastRational) -> Self::Output {
837 if rhs.is_zero() {
838 return FastRational::zero();
839 }
840 let quotient = self / rhs;
841 let floor_q = FastRational::from_integer(quotient.floor());
842 self - &(rhs * &floor_q)
843 }
844}
845
846impl RemAssign for FastRational {
847 #[inline]
848 fn rem_assign(&mut self, rhs: FastRational) {
849 *self = (&*self).rem(&rhs);
850 }
851}
852
853impl PartialEq for FastRational {
858 fn eq(&self, other: &Self) -> bool {
859 match (self, other) {
860 (
861 FastRational::Small { num: an, den: ad },
862 FastRational::Small { num: bn, den: bd },
863 ) => {
864 an == bn && ad == bd
866 }
867 (a, b) => {
868 a.to_big_rational() == b.to_big_rational()
870 }
871 }
872 }
873}
874
875impl Eq for FastRational {}
876
877impl PartialOrd for FastRational {
882 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
883 Some(self.cmp(other))
884 }
885}
886
887impl Ord for FastRational {
888 fn cmp(&self, other: &Self) -> Ordering {
889 match (self, other) {
890 (
891 FastRational::Small { num: an, den: ad },
892 FastRational::Small { num: bn, den: bd },
893 ) => {
894 if let (Some(lhs), Some(rhs)) = (an.checked_mul(*bd), bn.checked_mul(*ad)) {
898 return lhs.cmp(&rhs);
899 }
900 let big_a = BigRational::new(BigInt::from(*an), BigInt::from(*ad));
902 let big_b = BigRational::new(BigInt::from(*bn), BigInt::from(*bd));
903 big_a.cmp(&big_b)
904 }
905 (a, b) => {
906 let big_a = a.to_big_rational();
907 let big_b = b.to_big_rational();
908 big_a.cmp(&big_b)
909 }
910 }
911 }
912}
913
914impl Hash for FastRational {
919 fn hash<H: Hasher>(&self, state: &mut H) {
920 match self {
924 FastRational::Small { num, den } => {
925 num.hash(state);
926 den.hash(state);
927 }
928 FastRational::Big(b) => {
929 let reduced = b.reduced();
931 let n_opt: Option<i64> = reduced.numer().try_into().ok();
932 let d_opt: Option<i64> = reduced.denom().try_into().ok();
933 match (n_opt, d_opt) {
934 (Some(n), Some(d)) if d > 0 => {
935 n.hash(state);
936 d.hash(state);
937 }
938 (Some(n), Some(d)) if d < 0 => {
939 (-n).hash(state);
941 (-d).hash(state);
942 }
943 _ => {
944 let (n, d) = if reduced.denom().is_negative() {
947 (-reduced.numer(), -reduced.denom())
948 } else {
949 (reduced.numer().clone(), reduced.denom().clone())
950 };
951 n.hash(state);
952 d.hash(state);
953 }
954 }
955 }
956 }
957 }
958}
959
960impl fmt::Display for FastRational {
965 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
966 match self {
967 FastRational::Small { num, den } => {
968 if *den == 1 {
969 write!(f, "{}", num)
970 } else {
971 write!(f, "{}/{}", num, den)
972 }
973 }
974 FastRational::Big(b) => write!(f, "{}", b),
975 }
976 }
977}
978
979impl FastRational {
984 #[inline]
986 pub fn from_integer(n: BigInt) -> Self {
987 FastRational::from_bigint(&n)
988 }
989}
990
991impl FastRational {
996 #[inline]
998 pub fn max(self, other: Self) -> Self {
999 if self >= other { self } else { other }
1000 }
1001
1002 #[inline]
1004 pub fn min(self, other: Self) -> Self {
1005 if self <= other { self } else { other }
1006 }
1007}
1008
1009#[cfg(test)]
1014mod tests {
1015 use super::*;
1016 use std::collections::hash_map::DefaultHasher;
1017
1018 fn small(n: i64, d: i64) -> FastRational {
1019 FastRational::new_small(n, d)
1020 }
1021
1022 fn fr(n: i64) -> FastRational {
1023 FastRational::from(n)
1024 }
1025
1026 fn hash_of(val: &FastRational) -> u64 {
1027 let mut h = DefaultHasher::new();
1028 val.hash(&mut h);
1029 h.finish()
1030 }
1031
1032 #[test]
1035 fn test_new_small_normalization() {
1036 let r = small(2, 4);
1038 assert_eq!(r, small(1, 2));
1039 }
1040
1041 #[test]
1042 fn test_new_small_negative_den() {
1043 let r = small(3, -5);
1045 assert_eq!(r, small(-3, 5));
1046 }
1047
1048 #[test]
1049 fn test_new_small_zero() {
1050 let r = small(0, 42);
1051 assert_eq!(r, FastRational::zero());
1052 }
1053
1054 #[test]
1055 fn test_from_i64() {
1056 let r = FastRational::from(7i64);
1057 assert_eq!(r, small(7, 1));
1058 }
1059
1060 #[test]
1061 fn test_from_tuple() {
1062 let r = FastRational::from((6i64, 4i64));
1063 assert_eq!(r, small(3, 2));
1064 }
1065
1066 #[test]
1067 fn test_from_bigrational() {
1068 let br = BigRational::new(BigInt::from(10), BigInt::from(4));
1069 let r = FastRational::from(br);
1070 assert_eq!(r, small(5, 2));
1071 }
1072
1073 #[test]
1076 fn test_add() {
1077 let a = small(1, 2);
1079 let b = small(1, 3);
1080 assert_eq!(&a + &b, small(5, 6));
1081 }
1082
1083 #[test]
1084 fn test_sub() {
1085 let a = small(3, 4);
1087 let b = small(1, 4);
1088 assert_eq!(&a - &b, small(1, 2));
1089 }
1090
1091 #[test]
1092 fn test_mul() {
1093 let a = small(2, 3);
1095 let b = small(3, 4);
1096 assert_eq!(&a * &b, small(1, 2));
1097 }
1098
1099 #[test]
1100 fn test_div() {
1101 let a = small(2, 3);
1103 let b = small(4, 5);
1104 assert_eq!(&a / &b, small(5, 6));
1105 }
1106
1107 #[test]
1108 fn test_neg() {
1109 let r = small(3, 5);
1110 assert_eq!(-r, small(-3, 5));
1111 }
1112
1113 #[test]
1114 fn test_add_assign() {
1115 let mut a = small(1, 2);
1116 a += small(1, 3);
1117 assert_eq!(a, small(5, 6));
1118 }
1119
1120 #[test]
1121 fn test_mul_assign() {
1122 let mut a = small(2, 3);
1123 a *= small(3, 4);
1124 assert_eq!(a, small(1, 2));
1125 }
1126
1127 #[test]
1130 fn test_overflow_to_big() {
1131 let big_val = i64::MAX;
1132 let a = fr(big_val);
1133 let b = fr(big_val);
1134 let result = &a + &b;
1135 let expected = BigRational::from_integer(BigInt::from(big_val))
1137 + BigRational::from_integer(BigInt::from(big_val));
1138 assert_eq!(result.to_big_rational(), expected);
1139 }
1140
1141 #[test]
1144 fn test_ord() {
1145 assert!(small(1, 3) < small(1, 2));
1146 assert!(small(-1, 2) < small(1, 2));
1147 assert!(small(0, 1) == FastRational::zero());
1148 }
1149
1150 #[test]
1151 fn test_eq_cross_representation() {
1152 let s = small(1, 2);
1154 let b = FastRational::Big(Box::new(BigRational::new(BigInt::from(1), BigInt::from(2))));
1155 assert_eq!(s, b);
1156 }
1157
1158 #[test]
1161 fn test_hash_consistency() {
1162 let s = small(1, 2);
1163 let b = FastRational::Big(Box::new(BigRational::new(BigInt::from(1), BigInt::from(2))));
1164 assert_eq!(hash_of(&s), hash_of(&b));
1165 }
1166
1167 #[test]
1170 fn test_zero_one() {
1171 assert!(FastRational::zero().is_zero());
1172 assert!(FastRational::one().is_one());
1173 assert!(!FastRational::zero().is_one());
1174 assert!(!FastRational::one().is_zero());
1175 }
1176
1177 #[test]
1178 fn test_signed() {
1179 assert!(small(3, 5).is_positive());
1180 assert!(small(-3, 5).is_negative());
1181 assert!(!FastRational::zero().is_positive());
1182 assert!(!FastRational::zero().is_negative());
1183 }
1184
1185 #[test]
1186 fn test_abs() {
1187 assert_eq!(small(-3, 5).abs(), small(3, 5));
1188 assert_eq!(small(3, 5).abs(), small(3, 5));
1189 }
1190
1191 #[test]
1194 fn test_recip() {
1195 assert_eq!(small(3, 5).recip(), Some(small(5, 3)));
1196 assert_eq!(FastRational::zero().recip(), None);
1197 }
1198
1199 #[test]
1200 fn test_floor_ceil() {
1201 let r = small(7, 3);
1203 assert_eq!(r.floor(), BigInt::from(2));
1204 assert_eq!(r.ceil(), BigInt::from(3));
1205
1206 let r = small(-7, 3);
1208 assert_eq!(r.floor(), BigInt::from(-3));
1209 assert_eq!(r.ceil(), BigInt::from(-2));
1210 }
1211
1212 #[test]
1213 fn test_ceil_i64_max_boundary_no_overflow() {
1214 let r = FastRational::new_small(i64::MAX, 2);
1222 let big = r.to_big_rational();
1223 assert_eq!(r.ceil(), crate::rational::ceil(&big));
1224 assert_eq!(r.floor(), crate::rational::floor(&big));
1225 assert_eq!(r.ceil(), r.floor() + BigInt::from(1));
1227 }
1228
1229 #[test]
1230 fn test_ceil_i64_min_boundary() {
1231 let r = FastRational::new_small(i64::MIN, 2);
1234 assert_eq!(r.ceil(), BigInt::from(i64::MIN / 2));
1235 assert_eq!(r.floor(), BigInt::from(i64::MIN / 2));
1236 }
1237
1238 #[test]
1239 fn test_ceil_large_positive_non_integer() {
1240 let r = FastRational::new_small(i64::MAX - 1, 4);
1244 let ceil = r.ceil();
1245 let floor = r.floor();
1246 let big = r.to_big_rational();
1249 let expected_floor = crate::rational::floor(&big);
1250 let expected_ceil = crate::rational::ceil(&big);
1251 assert_eq!(floor, expected_floor);
1252 assert_eq!(ceil, expected_ceil);
1253 }
1254
1255 #[test]
1256 fn test_to_f64() {
1257 let r = small(1, 2);
1258 assert!((r.to_f64() - 0.5).abs() < 1e-10);
1259 }
1260
1261 #[test]
1262 fn test_to_big_rational_roundtrip() {
1263 let r = small(7, 13);
1264 let big = r.to_big_rational();
1265 let back = FastRational::from_big(big);
1266 assert_eq!(r, back);
1267 }
1268
1269 #[test]
1270 fn test_numer_denom() {
1271 let r = small(3, 7);
1272 assert_eq!(r.numer(), BigInt::from(3));
1273 assert_eq!(r.denom(), BigInt::from(7));
1274 }
1275
1276 #[test]
1277 fn test_is_integer() {
1278 assert!(fr(5).is_integer());
1279 assert!(!small(5, 3).is_integer());
1280 }
1281
1282 #[test]
1283 fn test_display() {
1284 assert_eq!(format!("{}", fr(5)), "5");
1285 assert_eq!(format!("{}", small(3, 7)), "3/7");
1286 assert_eq!(format!("{}", small(-1, 2)), "-1/2");
1287 }
1288
1289 #[test]
1290 fn test_max_min() {
1291 let a = small(1, 3);
1292 let b = small(1, 2);
1293 assert_eq!(a.clone().max(b.clone()), b);
1294 assert_eq!(a.clone().min(b.clone()), a);
1295 }
1296
1297 #[test]
1303 fn test_new_small_i64_min_odd_denominator_stays_irreducible() {
1304 let r = FastRational::new_small(i64::MIN, 7);
1307 assert_eq!(
1308 r,
1309 FastRational::Small {
1310 num: i64::MIN,
1311 den: 7
1312 }
1313 );
1314 let expected = BigRational::new(BigInt::from(i64::MIN), BigInt::from(7));
1316 assert_eq!(r.to_big_rational(), expected);
1317 }
1318
1319 #[test]
1320 fn test_new_small_i64_min_even_denominator_reduces_correctly() {
1321 let r = FastRational::new_small(i64::MIN, 2);
1324 assert_eq!(
1325 r,
1326 FastRational::Small {
1327 num: i64::MIN / 2,
1328 den: 1
1329 }
1330 );
1331 let expected = BigRational::new(BigInt::from(i64::MIN), BigInt::from(2));
1332 assert_eq!(r.to_big_rational(), expected);
1333 }
1334
1335 #[test]
1336 fn test_mul_small_i64_min_by_small_fraction() {
1337 let a = FastRational::Small {
1343 num: i64::MIN,
1344 den: 1,
1345 };
1346 let b = FastRational::Small { num: 1, den: 7 };
1347 let result = &a * &b;
1348
1349 let expected = BigRational::new(BigInt::from(i64::MIN), BigInt::from(7));
1350 assert_eq!(
1351 result.to_big_rational(),
1352 expected,
1353 "MIN * (1/7) must equal MIN/7 exactly, not truncate to an integer"
1354 );
1355 assert!(!result.is_integer());
1357 }
1358
1359 #[test]
1360 fn test_mul_small_i64_min_eq_hash_invariant_preserved() {
1361 let a = FastRational::Small {
1365 num: i64::MIN,
1366 den: 1,
1367 };
1368 let b = FastRational::Small { num: 1, den: 7 };
1369 let small_result = &a * &b;
1370
1371 let big_a = BigRational::from_integer(BigInt::from(i64::MIN));
1372 let big_b = BigRational::new(BigInt::one(), BigInt::from(7));
1373 let big_result = FastRational::from_big(big_a * big_b);
1374
1375 assert_eq!(small_result, big_result);
1376 assert_eq!(hash_of(&small_result), hash_of(&big_result));
1377 }
1378
1379 #[test]
1380 fn test_from_str_radix() {
1381 use num_traits::Num;
1382 let r = FastRational::from_str_radix("3/7", 10);
1383 assert!(r.is_ok());
1384 assert_eq!(r.ok(), Some(small(3, 7)));
1385
1386 let r2 = FastRational::from_str_radix("42", 10);
1387 assert!(r2.is_ok());
1388 assert_eq!(r2.ok(), Some(fr(42)));
1389 }
1390}