1#[allow(unused_imports)]
23use crate::prelude::*;
24use core::cmp::Ordering;
25use core::fmt;
26use core::hash::{Hash, Hasher};
27use core::ops::{
28 Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign,
29};
30use num_bigint::BigInt;
31use num_integer::Integer;
32use num_rational::BigRational;
33use num_traits::{One, Signed, Zero};
34
35#[derive(Clone, Debug)]
39pub enum FastRational {
40 Small {
42 num: i64,
44 den: i64,
46 },
47 Big(Box<BigRational>),
49}
50
51#[inline]
59fn gcd_i64(a: i64, b: i64) -> i64 {
60 let mut a = a.unsigned_abs();
62 let mut b = b.unsigned_abs();
63 if a == 0 {
64 return b as i64;
65 }
66 if b == 0 {
67 return a as i64;
68 }
69 let shift = (a | b).trailing_zeros();
71 a >>= a.trailing_zeros();
72 loop {
73 b >>= b.trailing_zeros();
74 if a > b {
75 core::mem::swap(&mut a, &mut b);
76 }
77 b -= a;
78 if b == 0 {
79 break;
80 }
81 }
82 (a << shift) as i64
83}
84
85impl FastRational {
90 #[inline]
106 pub fn new_small(num: i64, den: i64) -> Self {
107 debug_assert!(den != 0, "FastRational: denominator must not be zero");
108 if num == 0 {
109 return FastRational::Small { num: 0, den: 1 };
110 }
111 let (n, d) = if den < 0 {
112 match (num.checked_neg(), den.checked_neg()) {
115 (Some(nn), Some(dd)) => (nn, dd),
116 _ => {
117 let big = BigRational::new(BigInt::from(num), BigInt::from(den));
119 return FastRational::Big(Box::new(big));
120 }
121 }
122 } else {
123 (num, den)
124 };
125 let g = gcd_i64(n, d);
133 if g == 0 {
134 return FastRational::Small { num: 0, den: 1 };
135 }
136 FastRational::Small {
137 num: n / g,
138 den: d / g,
139 }
140 }
141
142 pub fn from_big(br: BigRational) -> Self {
144 let n_opt: Option<i64> = br.numer().try_into().ok();
146 let d_opt: Option<i64> = br.denom().try_into().ok();
147 match (n_opt, d_opt) {
148 (Some(n), Some(d)) if d != 0 => FastRational::new_small(n, d),
149 _ => FastRational::Big(Box::new(br)),
150 }
151 }
152
153 pub fn to_big_rational(&self) -> BigRational {
155 match self {
156 FastRational::Small { num, den } => {
157 BigRational::new(BigInt::from(*num), BigInt::from(*den))
158 }
159 FastRational::Big(b) => (**b).clone(),
160 }
161 }
162
163 #[inline]
165 pub fn to_f64(&self) -> f64 {
166 match self {
167 FastRational::Small { num, den } => *num as f64 / *den as f64,
168 FastRational::Big(b) => {
169 use num_traits::ToPrimitive;
170 b.numer().to_f64().unwrap_or(f64::NAN) / b.denom().to_f64().unwrap_or(f64::NAN)
171 }
172 }
173 }
174
175 pub fn recip(&self) -> Option<Self> {
177 match self {
178 FastRational::Small { num, den } => {
179 if *num == 0 {
180 None
181 } else {
182 Some(FastRational::new_small(*den, *num))
183 }
184 }
185 FastRational::Big(b) => {
186 if b.is_zero() {
187 None
188 } else {
189 Some(FastRational::from_big(b.recip()))
190 }
191 }
192 }
193 }
194
195 pub fn floor(&self) -> BigInt {
197 match self {
198 FastRational::Small { num, den } => {
199 if *den == 1 {
200 BigInt::from(*num)
201 } else {
202 let q = num.div_floor(den);
203 BigInt::from(q)
204 }
205 }
206 FastRational::Big(b) => crate::rational::floor(b),
207 }
208 }
209
210 pub fn ceil(&self) -> BigInt {
212 match self {
213 FastRational::Small { num, den } => {
214 if *den == 1 {
215 BigInt::from(*num)
216 } else {
217 let q = num.div_ceil(den);
226 BigInt::from(q)
227 }
228 }
229 FastRational::Big(b) => crate::rational::ceil(b),
230 }
231 }
232
233 pub fn abs(&self) -> Self {
235 match self {
236 FastRational::Small { num, den } => {
237 match num.checked_abs() {
238 Some(n) => FastRational::Small { num: n, den: *den },
239 None => {
240 let big = BigRational::new(BigInt::from(*num).abs(), BigInt::from(*den));
242 FastRational::from_big(big)
243 }
244 }
245 }
246 FastRational::Big(b) => FastRational::from_big(b.abs()),
247 }
248 }
249
250 pub fn numer(&self) -> BigInt {
252 match self {
253 FastRational::Small { num, .. } => BigInt::from(*num),
254 FastRational::Big(b) => b.numer().clone(),
255 }
256 }
257
258 pub fn denom(&self) -> BigInt {
260 match self {
261 FastRational::Small { den, .. } => BigInt::from(*den),
262 FastRational::Big(b) => b.denom().clone(),
263 }
264 }
265
266 #[inline]
268 pub fn is_integer(&self) -> bool {
269 match self {
270 FastRational::Small { den, .. } => *den == 1,
271 FastRational::Big(b) => b.is_integer(),
272 }
273 }
274
275 pub fn from_big_ref(br: &BigRational) -> Self {
277 let n_opt: Option<i64> = br.numer().try_into().ok();
278 let d_opt: Option<i64> = br.denom().try_into().ok();
279 match (n_opt, d_opt) {
280 (Some(n), Some(d)) if d != 0 => FastRational::new_small(n, d),
281 _ => FastRational::Big(Box::new(br.clone())),
282 }
283 }
284
285 pub fn from_bigint(bi: &BigInt) -> Self {
287 let n_opt: Option<i64> = bi.try_into().ok();
288 match n_opt {
289 Some(n) => FastRational::Small { num: n, den: 1 },
290 None => FastRational::Big(Box::new(BigRational::from_integer(bi.clone()))),
291 }
292 }
293}
294
295#[inline]
301fn add_small(a_num: i64, a_den: i64, b_num: i64, b_den: i64) -> FastRational {
302 if let (Some(ad), Some(cb), Some(bd)) = (
304 a_num.checked_mul(b_den),
305 b_num.checked_mul(a_den),
306 a_den.checked_mul(b_den),
307 ) && let Some(num) = ad.checked_add(cb)
308 {
309 return FastRational::new_small(num, bd);
310 }
311 let a = BigRational::new(BigInt::from(a_num), BigInt::from(a_den));
313 let b = BigRational::new(BigInt::from(b_num), BigInt::from(b_den));
314 FastRational::from_big(a + b)
315}
316
317#[inline]
319fn sub_small(a_num: i64, a_den: i64, b_num: i64, b_den: i64) -> FastRational {
320 if let (Some(ad), Some(cb), Some(bd)) = (
322 a_num.checked_mul(b_den),
323 b_num.checked_mul(a_den),
324 a_den.checked_mul(b_den),
325 ) && let Some(num) = ad.checked_sub(cb)
326 {
327 return FastRational::new_small(num, bd);
328 }
329 let a = BigRational::new(BigInt::from(a_num), BigInt::from(a_den));
330 let b = BigRational::new(BigInt::from(b_num), BigInt::from(b_den));
331 FastRational::from_big(a - b)
332}
333
334#[inline]
336fn mul_small(a_num: i64, a_den: i64, b_num: i64, b_den: i64) -> FastRational {
337 let g1 = gcd_i64(a_num, b_den);
343 let g2 = gcd_i64(b_num, a_den);
344 let an = if g1 != 0 { a_num / g1 } else { a_num };
345 let bd = if g1 != 0 { b_den / g1 } else { b_den };
346 let bn = if g2 != 0 { b_num / g2 } else { b_num };
347 let ad = if g2 != 0 { a_den / g2 } else { a_den };
348
349 if let (Some(num), Some(den)) = (an.checked_mul(bn), ad.checked_mul(bd)) {
350 return FastRational::new_small(num, den);
351 }
352 let a = BigRational::new(BigInt::from(a_num), BigInt::from(a_den));
353 let b = BigRational::new(BigInt::from(b_num), BigInt::from(b_den));
354 FastRational::from_big(a * b)
355}
356
357#[inline]
360fn div_small(a_num: i64, a_den: i64, b_num: i64, b_den: i64) -> Option<FastRational> {
361 if b_num == 0 {
362 return None;
363 }
364 if let (Some(num), Some(den)) = (a_num.checked_mul(b_den), a_den.checked_mul(b_num)) {
368 Some(FastRational::new_small(num, den))
369 } else {
370 let a = BigRational::new(BigInt::from(a_num), BigInt::from(a_den));
371 let b = BigRational::new(BigInt::from(b_num), BigInt::from(b_den));
372 Some(FastRational::from_big(a / b))
373 }
374}
375
376impl From<i64> for FastRational {
381 #[inline]
382 fn from(n: i64) -> Self {
383 FastRational::Small { num: n, den: 1 }
384 }
385}
386
387impl From<(i64, i64)> for FastRational {
388 fn from((n, d): (i64, i64)) -> Self {
389 if d == 0 {
390 FastRational::Small { num: 0, den: 1 }
392 } else {
393 FastRational::new_small(n, d)
394 }
395 }
396}
397
398impl From<BigRational> for FastRational {
399 fn from(br: BigRational) -> Self {
400 FastRational::from_big(br)
401 }
402}
403
404impl From<BigInt> for FastRational {
405 fn from(bi: BigInt) -> Self {
406 FastRational::from_bigint(&bi)
407 }
408}
409
410impl From<&BigRational> for FastRational {
411 fn from(br: &BigRational) -> Self {
412 FastRational::from_big_ref(br)
413 }
414}
415
416impl Zero for FastRational {
421 #[inline]
422 fn zero() -> Self {
423 FastRational::Small { num: 0, den: 1 }
424 }
425
426 #[inline]
427 fn is_zero(&self) -> bool {
428 match self {
429 FastRational::Small { num, .. } => *num == 0,
430 FastRational::Big(b) => b.is_zero(),
431 }
432 }
433}
434
435impl One for FastRational {
436 #[inline]
437 fn one() -> Self {
438 FastRational::Small { num: 1, den: 1 }
439 }
440
441 #[inline]
442 fn is_one(&self) -> bool {
443 match self {
444 FastRational::Small { num, den } => *num == 1 && *den == 1,
445 FastRational::Big(b) => b.is_one(),
446 }
447 }
448}
449
450impl Signed for FastRational {
455 fn abs(&self) -> Self {
456 FastRational::abs(self)
457 }
458
459 fn abs_sub(&self, other: &Self) -> Self {
460 let diff = self - other;
461 if diff.is_positive() {
462 diff
463 } else {
464 FastRational::zero()
465 }
466 }
467
468 fn signum(&self) -> Self {
469 if self.is_zero() {
470 FastRational::zero()
471 } else if self.is_positive() {
472 FastRational::one()
473 } else {
474 FastRational::Small { num: -1, den: 1 }
475 }
476 }
477
478 fn is_positive(&self) -> bool {
479 match self {
480 FastRational::Small { num, .. } => *num > 0,
481 FastRational::Big(b) => b.is_positive(),
482 }
483 }
484
485 fn is_negative(&self) -> bool {
486 match self {
487 FastRational::Small { num, .. } => *num < 0,
488 FastRational::Big(b) => b.is_negative(),
489 }
490 }
491}
492
493impl num_traits::Num for FastRational {
494 type FromStrRadixErr = String;
495
496 fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
497 if let Some((num_str, den_str)) = str.split_once('/') {
499 let num = BigInt::from_str_radix(num_str.trim(), radix)
500 .map_err(|e| format!("invalid numerator: {}", e))?;
501 let den = BigInt::from_str_radix(den_str.trim(), radix)
502 .map_err(|e| format!("invalid denominator: {}", e))?;
503 if den.is_zero() {
504 return Err("denominator is zero".to_string());
505 }
506 Ok(FastRational::from_big(BigRational::new(num, den)))
507 } else {
508 let num = BigInt::from_str_radix(str.trim(), radix)
509 .map_err(|e| format!("invalid integer: {}", e))?;
510 Ok(FastRational::from_bigint(&num))
511 }
512 }
513}
514
515impl Neg for FastRational {
520 type Output = FastRational;
521
522 #[inline]
523 fn neg(self) -> Self::Output {
524 match self {
525 FastRational::Small { num, den } => match num.checked_neg() {
526 Some(n) => FastRational::Small { num: n, den },
527 None => {
528 let big = BigRational::new(-BigInt::from(num), BigInt::from(den));
529 FastRational::Big(Box::new(big))
530 }
531 },
532 FastRational::Big(b) => FastRational::from_big(-*b),
533 }
534 }
535}
536
537impl Neg for &FastRational {
538 type Output = FastRational;
539
540 #[inline]
541 fn neg(self) -> Self::Output {
542 match self {
543 FastRational::Small { num, den } => match num.checked_neg() {
544 Some(n) => FastRational::Small { num: n, den: *den },
545 None => {
546 let big = BigRational::new(-BigInt::from(*num), BigInt::from(*den));
547 FastRational::Big(Box::new(big))
548 }
549 },
550 FastRational::Big(b) => FastRational::from_big(-((**b).clone())),
551 }
552 }
553}
554
555impl Add 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 self.add(&rhs)
583 }
584}
585
586impl Add<&FastRational> for &FastRational {
587 type Output = FastRational;
588
589 #[inline]
590 fn add(self, rhs: &FastRational) -> Self::Output {
591 match (self, rhs) {
592 (
593 FastRational::Small { num: an, den: ad },
594 FastRational::Small { num: bn, den: bd },
595 ) => add_small(*an, *ad, *bn, *bd),
596 (a, b) => {
597 let big_a = a.to_big_rational();
598 let big_b = b.to_big_rational();
599 FastRational::from_big(big_a + big_b)
600 }
601 }
602 }
603}
604
605impl AddAssign for FastRational {
606 #[inline]
607 fn add_assign(&mut self, rhs: FastRational) {
608 *self = (&*self) + &rhs;
609 }
610}
611
612impl AddAssign<&FastRational> for FastRational {
613 #[inline]
614 fn add_assign(&mut self, rhs: &FastRational) {
615 *self = (&*self) + rhs;
616 }
617}
618
619impl Sub 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 self.sub(&rhs)
647 }
648}
649
650impl Sub<&FastRational> for &FastRational {
651 type Output = FastRational;
652
653 #[inline]
654 fn sub(self, rhs: &FastRational) -> Self::Output {
655 match (self, rhs) {
656 (
657 FastRational::Small { num: an, den: ad },
658 FastRational::Small { num: bn, den: bd },
659 ) => sub_small(*an, *ad, *bn, *bd),
660 (a, b) => {
661 let big_a = a.to_big_rational();
662 let big_b = b.to_big_rational();
663 FastRational::from_big(big_a - big_b)
664 }
665 }
666 }
667}
668
669impl SubAssign for FastRational {
670 #[inline]
671 fn sub_assign(&mut self, rhs: FastRational) {
672 *self = (&*self) - &rhs;
673 }
674}
675
676impl SubAssign<&FastRational> for FastRational {
677 #[inline]
678 fn sub_assign(&mut self, rhs: &FastRational) {
679 *self = (&*self) - rhs;
680 }
681}
682
683impl Mul 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 self.mul(&rhs)
711 }
712}
713
714impl Mul<&FastRational> for &FastRational {
715 type Output = FastRational;
716
717 #[inline]
718 fn mul(self, rhs: &FastRational) -> Self::Output {
719 match (self, rhs) {
720 (
721 FastRational::Small { num: an, den: ad },
722 FastRational::Small { num: bn, den: bd },
723 ) => mul_small(*an, *ad, *bn, *bd),
724 (a, b) => {
725 let big_a = a.to_big_rational();
726 let big_b = b.to_big_rational();
727 FastRational::from_big(big_a * big_b)
728 }
729 }
730 }
731}
732
733impl MulAssign for FastRational {
734 #[inline]
735 fn mul_assign(&mut self, rhs: FastRational) {
736 *self = (&*self) * &rhs;
737 }
738}
739
740impl MulAssign<&FastRational> for FastRational {
741 #[inline]
742 fn mul_assign(&mut self, rhs: &FastRational) {
743 *self = (&*self) * rhs;
744 }
745}
746
747impl Div for FastRational {
752 type Output = FastRational;
753
754 #[inline]
762 fn div(self, rhs: FastRational) -> Self::Output {
763 (&self).div(&rhs)
764 }
765}
766
767impl Div<&FastRational> for FastRational {
768 type Output = FastRational;
769
770 #[inline]
771 fn div(self, rhs: &FastRational) -> Self::Output {
772 (&self).div(rhs)
773 }
774}
775
776impl Div<FastRational> for &FastRational {
777 type Output = FastRational;
778
779 #[inline]
780 fn div(self, rhs: FastRational) -> Self::Output {
781 self.div(&rhs)
782 }
783}
784
785impl Div<&FastRational> for &FastRational {
786 type Output = FastRational;
787
788 #[inline]
789 fn div(self, rhs: &FastRational) -> Self::Output {
790 match (self, rhs) {
791 (
792 FastRational::Small { num: an, den: ad },
793 FastRational::Small { num: bn, den: bd },
794 ) => match div_small(*an, *ad, *bn, *bd) {
795 Some(r) => r,
796 None => panic!("FastRational: division by zero"),
802 },
803 (a, b) => {
804 if b.is_zero() {
805 panic!("FastRational: division by zero");
806 }
807 let big_a = a.to_big_rational();
808 let big_b = b.to_big_rational();
809 FastRational::from_big(big_a / big_b)
810 }
811 }
812 }
813}
814
815impl DivAssign for FastRational {
816 #[inline]
817 fn div_assign(&mut self, rhs: FastRational) {
818 *self = (&*self) / &rhs;
819 }
820}
821
822impl DivAssign<&FastRational> for FastRational {
823 #[inline]
824 fn div_assign(&mut self, rhs: &FastRational) {
825 *self = (&*self) / rhs;
826 }
827}
828
829impl Rem for FastRational {
834 type Output = FastRational;
835
836 #[inline]
840 fn rem(self, rhs: FastRational) -> Self::Output {
841 (&self).rem(&rhs)
842 }
843}
844
845impl Rem<&FastRational> for &FastRational {
846 type Output = FastRational;
847
848 #[inline]
849 fn rem(self, rhs: &FastRational) -> Self::Output {
850 if rhs.is_zero() {
851 return FastRational::zero();
852 }
853 let quotient = self / rhs;
854 let floor_q = FastRational::from_integer(quotient.floor());
855 self - &(rhs * &floor_q)
856 }
857}
858
859impl RemAssign for FastRational {
860 #[inline]
861 fn rem_assign(&mut self, rhs: FastRational) {
862 *self = (&*self).rem(&rhs);
863 }
864}
865
866impl PartialEq for FastRational {
871 fn eq(&self, other: &Self) -> bool {
872 match (self, other) {
873 (
874 FastRational::Small { num: an, den: ad },
875 FastRational::Small { num: bn, den: bd },
876 ) => {
877 an == bn && ad == bd
879 }
880 (a, b) => {
881 a.to_big_rational() == b.to_big_rational()
883 }
884 }
885 }
886}
887
888impl Eq for FastRational {}
889
890impl PartialOrd for FastRational {
895 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
896 Some(self.cmp(other))
897 }
898}
899
900impl Ord for FastRational {
901 fn cmp(&self, other: &Self) -> Ordering {
902 match (self, other) {
903 (
904 FastRational::Small { num: an, den: ad },
905 FastRational::Small { num: bn, den: bd },
906 ) => {
907 if let (Some(lhs), Some(rhs)) = (an.checked_mul(*bd), bn.checked_mul(*ad)) {
911 return lhs.cmp(&rhs);
912 }
913 let big_a = BigRational::new(BigInt::from(*an), BigInt::from(*ad));
915 let big_b = BigRational::new(BigInt::from(*bn), BigInt::from(*bd));
916 big_a.cmp(&big_b)
917 }
918 (a, b) => {
919 let big_a = a.to_big_rational();
920 let big_b = b.to_big_rational();
921 big_a.cmp(&big_b)
922 }
923 }
924 }
925}
926
927impl Hash for FastRational {
932 fn hash<H: Hasher>(&self, state: &mut H) {
933 match self {
937 FastRational::Small { num, den } => {
938 num.hash(state);
939 den.hash(state);
940 }
941 FastRational::Big(b) => {
942 let reduced = b.reduced();
944 let n_opt: Option<i64> = reduced.numer().try_into().ok();
945 let d_opt: Option<i64> = reduced.denom().try_into().ok();
946 match (n_opt, d_opt) {
947 (Some(n), Some(d)) if d > 0 => {
948 n.hash(state);
949 d.hash(state);
950 }
951 (Some(n), Some(d)) if d < 0 => {
952 (-n).hash(state);
954 (-d).hash(state);
955 }
956 _ => {
957 let (n, d) = if reduced.denom().is_negative() {
960 (-reduced.numer(), -reduced.denom())
961 } else {
962 (reduced.numer().clone(), reduced.denom().clone())
963 };
964 n.hash(state);
965 d.hash(state);
966 }
967 }
968 }
969 }
970 }
971}
972
973impl fmt::Display for FastRational {
978 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
979 match self {
980 FastRational::Small { num, den } => {
981 if *den == 1 {
982 write!(f, "{}", num)
983 } else {
984 write!(f, "{}/{}", num, den)
985 }
986 }
987 FastRational::Big(b) => write!(f, "{}", b),
988 }
989 }
990}
991
992impl FastRational {
997 #[inline]
999 pub fn from_integer(n: BigInt) -> Self {
1000 FastRational::from_bigint(&n)
1001 }
1002}
1003
1004impl FastRational {
1009 #[inline]
1011 pub fn max(self, other: Self) -> Self {
1012 if self >= other { self } else { other }
1013 }
1014
1015 #[inline]
1017 pub fn min(self, other: Self) -> Self {
1018 if self <= other { self } else { other }
1019 }
1020}
1021
1022#[cfg(test)]
1027mod tests {
1028 use super::*;
1029 use std::collections::hash_map::DefaultHasher;
1030
1031 fn small(n: i64, d: i64) -> FastRational {
1032 FastRational::new_small(n, d)
1033 }
1034
1035 fn fr(n: i64) -> FastRational {
1036 FastRational::from(n)
1037 }
1038
1039 fn hash_of(val: &FastRational) -> u64 {
1040 let mut h = DefaultHasher::new();
1041 val.hash(&mut h);
1042 h.finish()
1043 }
1044
1045 #[test]
1048 fn test_new_small_normalization() {
1049 let r = small(2, 4);
1051 assert_eq!(r, small(1, 2));
1052 }
1053
1054 #[test]
1055 fn test_new_small_negative_den() {
1056 let r = small(3, -5);
1058 assert_eq!(r, small(-3, 5));
1059 }
1060
1061 #[test]
1062 fn test_new_small_zero() {
1063 let r = small(0, 42);
1064 assert_eq!(r, FastRational::zero());
1065 }
1066
1067 #[test]
1068 fn test_from_i64() {
1069 let r = FastRational::from(7i64);
1070 assert_eq!(r, small(7, 1));
1071 }
1072
1073 #[test]
1074 fn test_from_tuple() {
1075 let r = FastRational::from((6i64, 4i64));
1076 assert_eq!(r, small(3, 2));
1077 }
1078
1079 #[test]
1080 fn test_from_bigrational() {
1081 let br = BigRational::new(BigInt::from(10), BigInt::from(4));
1082 let r = FastRational::from(br);
1083 assert_eq!(r, small(5, 2));
1084 }
1085
1086 #[test]
1089 fn test_add() {
1090 let a = small(1, 2);
1092 let b = small(1, 3);
1093 assert_eq!(&a + &b, small(5, 6));
1094 }
1095
1096 #[test]
1097 fn test_sub() {
1098 let a = small(3, 4);
1100 let b = small(1, 4);
1101 assert_eq!(&a - &b, small(1, 2));
1102 }
1103
1104 #[test]
1105 fn test_mul() {
1106 let a = small(2, 3);
1108 let b = small(3, 4);
1109 assert_eq!(&a * &b, small(1, 2));
1110 }
1111
1112 #[test]
1113 fn test_div() {
1114 let a = small(2, 3);
1116 let b = small(4, 5);
1117 assert_eq!(&a / &b, small(5, 6));
1118 }
1119
1120 #[test]
1121 fn test_neg() {
1122 let r = small(3, 5);
1123 assert_eq!(-r, small(-3, 5));
1124 }
1125
1126 #[test]
1127 fn test_add_assign() {
1128 let mut a = small(1, 2);
1129 a += small(1, 3);
1130 assert_eq!(a, small(5, 6));
1131 }
1132
1133 #[test]
1134 fn test_mul_assign() {
1135 let mut a = small(2, 3);
1136 a *= small(3, 4);
1137 assert_eq!(a, small(1, 2));
1138 }
1139
1140 #[test]
1143 fn test_overflow_to_big() {
1144 let big_val = i64::MAX;
1145 let a = fr(big_val);
1146 let b = fr(big_val);
1147 let result = &a + &b;
1148 let expected = BigRational::from_integer(BigInt::from(big_val))
1150 + BigRational::from_integer(BigInt::from(big_val));
1151 assert_eq!(result.to_big_rational(), expected);
1152 }
1153
1154 #[test]
1157 fn test_ord() {
1158 assert!(small(1, 3) < small(1, 2));
1159 assert!(small(-1, 2) < small(1, 2));
1160 assert!(small(0, 1) == FastRational::zero());
1161 }
1162
1163 #[test]
1164 fn test_eq_cross_representation() {
1165 let s = small(1, 2);
1167 let b = FastRational::Big(Box::new(BigRational::new(BigInt::from(1), BigInt::from(2))));
1168 assert_eq!(s, b);
1169 }
1170
1171 #[test]
1174 fn test_hash_consistency() {
1175 let s = small(1, 2);
1176 let b = FastRational::Big(Box::new(BigRational::new(BigInt::from(1), BigInt::from(2))));
1177 assert_eq!(hash_of(&s), hash_of(&b));
1178 }
1179
1180 #[test]
1183 fn test_zero_one() {
1184 assert!(FastRational::zero().is_zero());
1185 assert!(FastRational::one().is_one());
1186 assert!(!FastRational::zero().is_one());
1187 assert!(!FastRational::one().is_zero());
1188 }
1189
1190 #[test]
1191 fn test_signed() {
1192 assert!(small(3, 5).is_positive());
1193 assert!(small(-3, 5).is_negative());
1194 assert!(!FastRational::zero().is_positive());
1195 assert!(!FastRational::zero().is_negative());
1196 }
1197
1198 #[test]
1199 fn test_abs() {
1200 assert_eq!(small(-3, 5).abs(), small(3, 5));
1201 assert_eq!(small(3, 5).abs(), small(3, 5));
1202 }
1203
1204 #[test]
1207 fn test_recip() {
1208 assert_eq!(small(3, 5).recip(), Some(small(5, 3)));
1209 assert_eq!(FastRational::zero().recip(), None);
1210 }
1211
1212 #[test]
1213 fn test_floor_ceil() {
1214 let r = small(7, 3);
1216 assert_eq!(r.floor(), BigInt::from(2));
1217 assert_eq!(r.ceil(), BigInt::from(3));
1218
1219 let r = small(-7, 3);
1221 assert_eq!(r.floor(), BigInt::from(-3));
1222 assert_eq!(r.ceil(), BigInt::from(-2));
1223 }
1224
1225 #[test]
1226 fn test_ceil_i64_max_boundary_no_overflow() {
1227 let r = FastRational::new_small(i64::MAX, 2);
1235 let big = r.to_big_rational();
1236 assert_eq!(r.ceil(), crate::rational::ceil(&big));
1237 assert_eq!(r.floor(), crate::rational::floor(&big));
1238 assert_eq!(r.ceil(), r.floor() + BigInt::from(1));
1240 }
1241
1242 #[test]
1243 fn test_ceil_i64_min_boundary() {
1244 let r = FastRational::new_small(i64::MIN, 2);
1247 assert_eq!(r.ceil(), BigInt::from(i64::MIN / 2));
1248 assert_eq!(r.floor(), BigInt::from(i64::MIN / 2));
1249 }
1250
1251 #[test]
1252 fn test_ceil_large_positive_non_integer() {
1253 let r = FastRational::new_small(i64::MAX - 1, 4);
1257 let ceil = r.ceil();
1258 let floor = r.floor();
1259 let big = r.to_big_rational();
1262 let expected_floor = crate::rational::floor(&big);
1263 let expected_ceil = crate::rational::ceil(&big);
1264 assert_eq!(floor, expected_floor);
1265 assert_eq!(ceil, expected_ceil);
1266 }
1267
1268 #[test]
1269 fn test_to_f64() {
1270 let r = small(1, 2);
1271 assert!((r.to_f64() - 0.5).abs() < 1e-10);
1272 }
1273
1274 #[test]
1275 fn test_to_big_rational_roundtrip() {
1276 let r = small(7, 13);
1277 let big = r.to_big_rational();
1278 let back = FastRational::from_big(big);
1279 assert_eq!(r, back);
1280 }
1281
1282 #[test]
1283 fn test_numer_denom() {
1284 let r = small(3, 7);
1285 assert_eq!(r.numer(), BigInt::from(3));
1286 assert_eq!(r.denom(), BigInt::from(7));
1287 }
1288
1289 #[test]
1290 fn test_is_integer() {
1291 assert!(fr(5).is_integer());
1292 assert!(!small(5, 3).is_integer());
1293 }
1294
1295 #[test]
1296 fn test_display() {
1297 assert_eq!(format!("{}", fr(5)), "5");
1298 assert_eq!(format!("{}", small(3, 7)), "3/7");
1299 assert_eq!(format!("{}", small(-1, 2)), "-1/2");
1300 }
1301
1302 #[test]
1303 fn test_max_min() {
1304 let a = small(1, 3);
1305 let b = small(1, 2);
1306 assert_eq!(a.clone().max(b.clone()), b);
1307 assert_eq!(a.clone().min(b.clone()), a);
1308 }
1309
1310 #[test]
1316 fn test_new_small_i64_min_odd_denominator_stays_irreducible() {
1317 let r = FastRational::new_small(i64::MIN, 7);
1320 assert_eq!(
1321 r,
1322 FastRational::Small {
1323 num: i64::MIN,
1324 den: 7
1325 }
1326 );
1327 let expected = BigRational::new(BigInt::from(i64::MIN), BigInt::from(7));
1329 assert_eq!(r.to_big_rational(), expected);
1330 }
1331
1332 #[test]
1333 fn test_new_small_i64_min_even_denominator_reduces_correctly() {
1334 let r = FastRational::new_small(i64::MIN, 2);
1337 assert_eq!(
1338 r,
1339 FastRational::Small {
1340 num: i64::MIN / 2,
1341 den: 1
1342 }
1343 );
1344 let expected = BigRational::new(BigInt::from(i64::MIN), BigInt::from(2));
1345 assert_eq!(r.to_big_rational(), expected);
1346 }
1347
1348 #[test]
1349 fn test_mul_small_i64_min_by_small_fraction() {
1350 let a = FastRational::Small {
1356 num: i64::MIN,
1357 den: 1,
1358 };
1359 let b = FastRational::Small { num: 1, den: 7 };
1360 let result = &a * &b;
1361
1362 let expected = BigRational::new(BigInt::from(i64::MIN), BigInt::from(7));
1363 assert_eq!(
1364 result.to_big_rational(),
1365 expected,
1366 "MIN * (1/7) must equal MIN/7 exactly, not truncate to an integer"
1367 );
1368 assert!(!result.is_integer());
1370 }
1371
1372 #[test]
1373 fn test_mul_small_i64_min_eq_hash_invariant_preserved() {
1374 let a = FastRational::Small {
1378 num: i64::MIN,
1379 den: 1,
1380 };
1381 let b = FastRational::Small { num: 1, den: 7 };
1382 let small_result = &a * &b;
1383
1384 let big_a = BigRational::from_integer(BigInt::from(i64::MIN));
1385 let big_b = BigRational::new(BigInt::one(), BigInt::from(7));
1386 let big_result = FastRational::from_big(big_a * big_b);
1387
1388 assert_eq!(small_result, big_result);
1389 assert_eq!(hash_of(&small_result), hash_of(&big_result));
1390 }
1391
1392 #[test]
1393 fn test_from_str_radix() {
1394 use num_traits::Num;
1395 let r = FastRational::from_str_radix("3/7", 10);
1396 assert!(r.is_ok());
1397 assert_eq!(r.ok(), Some(small(3, 7)));
1398
1399 let r2 = FastRational::from_str_radix("42", 10);
1400 assert!(r2.is_ok());
1401 assert_eq!(r2.ok(), Some(fr(42)));
1402 }
1403}