#[allow(unused_imports)]
use crate::prelude::*;
use core::cmp::Ordering;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::ops::{
Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign,
};
use num_bigint::BigInt;
use num_integer::Integer;
use num_rational::BigRational;
use num_traits::{One, Signed, Zero};
#[derive(Clone, Debug)]
pub enum FastRational {
Small {
num: i64,
den: i64,
},
Big(Box<BigRational>),
}
#[inline]
fn gcd_i64(a: i64, b: i64) -> i64 {
let mut a = a.unsigned_abs();
let mut b = b.unsigned_abs();
if a == 0 {
return b as i64;
}
if b == 0 {
return a as i64;
}
let shift = (a | b).trailing_zeros();
a >>= a.trailing_zeros();
loop {
b >>= b.trailing_zeros();
if a > b {
core::mem::swap(&mut a, &mut b);
}
b -= a;
if b == 0 {
break;
}
}
(a << shift) as i64
}
impl FastRational {
#[inline]
pub fn new_small(num: i64, den: i64) -> Self {
debug_assert!(den != 0, "FastRational: denominator must not be zero");
if num == 0 {
return FastRational::Small { num: 0, den: 1 };
}
let (n, d) = if den < 0 {
match (num.checked_neg(), den.checked_neg()) {
(Some(nn), Some(dd)) => (nn, dd),
_ => {
let big = BigRational::new(BigInt::from(num), BigInt::from(den));
return FastRational::Big(Box::new(big));
}
}
} else {
(num, den)
};
let g = gcd_i64(n, d);
if g == 0 {
return FastRational::Small { num: 0, den: 1 };
}
FastRational::Small {
num: n / g,
den: d / g,
}
}
pub fn from_big(br: BigRational) -> Self {
let n_opt: Option<i64> = br.numer().try_into().ok();
let d_opt: Option<i64> = br.denom().try_into().ok();
match (n_opt, d_opt) {
(Some(n), Some(d)) if d != 0 => FastRational::new_small(n, d),
_ => FastRational::Big(Box::new(br)),
}
}
pub fn to_big_rational(&self) -> BigRational {
match self {
FastRational::Small { num, den } => {
BigRational::new(BigInt::from(*num), BigInt::from(*den))
}
FastRational::Big(b) => (**b).clone(),
}
}
#[inline]
pub fn to_f64(&self) -> f64 {
match self {
FastRational::Small { num, den } => *num as f64 / *den as f64,
FastRational::Big(b) => {
use num_traits::ToPrimitive;
b.numer().to_f64().unwrap_or(f64::NAN) / b.denom().to_f64().unwrap_or(f64::NAN)
}
}
}
pub fn recip(&self) -> Option<Self> {
match self {
FastRational::Small { num, den } => {
if *num == 0 {
None
} else {
Some(FastRational::new_small(*den, *num))
}
}
FastRational::Big(b) => {
if b.is_zero() {
None
} else {
Some(FastRational::from_big(b.recip()))
}
}
}
}
pub fn floor(&self) -> BigInt {
match self {
FastRational::Small { num, den } => {
if *den == 1 {
BigInt::from(*num)
} else {
let q = num.div_floor(den);
BigInt::from(q)
}
}
FastRational::Big(b) => crate::rational::floor(b),
}
}
pub fn ceil(&self) -> BigInt {
match self {
FastRational::Small { num, den } => {
if *den == 1 {
BigInt::from(*num)
} else {
let q = num.div_ceil(den);
BigInt::from(q)
}
}
FastRational::Big(b) => crate::rational::ceil(b),
}
}
pub fn abs(&self) -> Self {
match self {
FastRational::Small { num, den } => {
match num.checked_abs() {
Some(n) => FastRational::Small { num: n, den: *den },
None => {
let big = BigRational::new(BigInt::from(*num).abs(), BigInt::from(*den));
FastRational::from_big(big)
}
}
}
FastRational::Big(b) => FastRational::from_big(b.abs()),
}
}
pub fn numer(&self) -> BigInt {
match self {
FastRational::Small { num, .. } => BigInt::from(*num),
FastRational::Big(b) => b.numer().clone(),
}
}
pub fn denom(&self) -> BigInt {
match self {
FastRational::Small { den, .. } => BigInt::from(*den),
FastRational::Big(b) => b.denom().clone(),
}
}
#[inline]
pub fn is_integer(&self) -> bool {
match self {
FastRational::Small { den, .. } => *den == 1,
FastRational::Big(b) => b.is_integer(),
}
}
pub fn from_big_ref(br: &BigRational) -> Self {
let n_opt: Option<i64> = br.numer().try_into().ok();
let d_opt: Option<i64> = br.denom().try_into().ok();
match (n_opt, d_opt) {
(Some(n), Some(d)) if d != 0 => FastRational::new_small(n, d),
_ => FastRational::Big(Box::new(br.clone())),
}
}
pub fn from_bigint(bi: &BigInt) -> Self {
let n_opt: Option<i64> = bi.try_into().ok();
match n_opt {
Some(n) => FastRational::Small { num: n, den: 1 },
None => FastRational::Big(Box::new(BigRational::from_integer(bi.clone()))),
}
}
}
#[inline]
fn add_small(a_num: i64, a_den: i64, b_num: i64, b_den: i64) -> FastRational {
if let (Some(ad), Some(cb), Some(bd)) = (
a_num.checked_mul(b_den),
b_num.checked_mul(a_den),
a_den.checked_mul(b_den),
) && let Some(num) = ad.checked_add(cb)
{
return FastRational::new_small(num, bd);
}
let a = BigRational::new(BigInt::from(a_num), BigInt::from(a_den));
let b = BigRational::new(BigInt::from(b_num), BigInt::from(b_den));
FastRational::from_big(a + b)
}
#[inline]
fn sub_small(a_num: i64, a_den: i64, b_num: i64, b_den: i64) -> FastRational {
if let (Some(ad), Some(cb), Some(bd)) = (
a_num.checked_mul(b_den),
b_num.checked_mul(a_den),
a_den.checked_mul(b_den),
) && let Some(num) = ad.checked_sub(cb)
{
return FastRational::new_small(num, bd);
}
let a = BigRational::new(BigInt::from(a_num), BigInt::from(a_den));
let b = BigRational::new(BigInt::from(b_num), BigInt::from(b_den));
FastRational::from_big(a - b)
}
#[inline]
fn mul_small(a_num: i64, a_den: i64, b_num: i64, b_den: i64) -> FastRational {
let g1 = gcd_i64(a_num, b_den);
let g2 = gcd_i64(b_num, a_den);
let an = if g1 != 0 { a_num / g1 } else { a_num };
let bd = if g1 != 0 { b_den / g1 } else { b_den };
let bn = if g2 != 0 { b_num / g2 } else { b_num };
let ad = if g2 != 0 { a_den / g2 } else { a_den };
if let (Some(num), Some(den)) = (an.checked_mul(bn), ad.checked_mul(bd)) {
return FastRational::new_small(num, den);
}
let a = BigRational::new(BigInt::from(a_num), BigInt::from(a_den));
let b = BigRational::new(BigInt::from(b_num), BigInt::from(b_den));
FastRational::from_big(a * b)
}
#[inline]
fn div_small(a_num: i64, a_den: i64, b_num: i64, b_den: i64) -> Option<FastRational> {
if b_num == 0 {
return None;
}
if let (Some(num), Some(den)) = (a_num.checked_mul(b_den), a_den.checked_mul(b_num)) {
Some(FastRational::new_small(num, den))
} else {
let a = BigRational::new(BigInt::from(a_num), BigInt::from(a_den));
let b = BigRational::new(BigInt::from(b_num), BigInt::from(b_den));
Some(FastRational::from_big(a / b))
}
}
impl From<i64> for FastRational {
#[inline]
fn from(n: i64) -> Self {
FastRational::Small { num: n, den: 1 }
}
}
impl From<(i64, i64)> for FastRational {
fn from((n, d): (i64, i64)) -> Self {
if d == 0 {
FastRational::Small { num: 0, den: 1 }
} else {
FastRational::new_small(n, d)
}
}
}
impl From<BigRational> for FastRational {
fn from(br: BigRational) -> Self {
FastRational::from_big(br)
}
}
impl From<BigInt> for FastRational {
fn from(bi: BigInt) -> Self {
FastRational::from_bigint(&bi)
}
}
impl From<&BigRational> for FastRational {
fn from(br: &BigRational) -> Self {
FastRational::from_big_ref(br)
}
}
impl Zero for FastRational {
#[inline]
fn zero() -> Self {
FastRational::Small { num: 0, den: 1 }
}
#[inline]
fn is_zero(&self) -> bool {
match self {
FastRational::Small { num, .. } => *num == 0,
FastRational::Big(b) => b.is_zero(),
}
}
}
impl One for FastRational {
#[inline]
fn one() -> Self {
FastRational::Small { num: 1, den: 1 }
}
#[inline]
fn is_one(&self) -> bool {
match self {
FastRational::Small { num, den } => *num == 1 && *den == 1,
FastRational::Big(b) => b.is_one(),
}
}
}
impl Signed for FastRational {
fn abs(&self) -> Self {
FastRational::abs(self)
}
fn abs_sub(&self, other: &Self) -> Self {
let diff = self - other;
if diff.is_positive() {
diff
} else {
FastRational::zero()
}
}
fn signum(&self) -> Self {
if self.is_zero() {
FastRational::zero()
} else if self.is_positive() {
FastRational::one()
} else {
FastRational::Small { num: -1, den: 1 }
}
}
fn is_positive(&self) -> bool {
match self {
FastRational::Small { num, .. } => *num > 0,
FastRational::Big(b) => b.is_positive(),
}
}
fn is_negative(&self) -> bool {
match self {
FastRational::Small { num, .. } => *num < 0,
FastRational::Big(b) => b.is_negative(),
}
}
}
impl num_traits::Num for FastRational {
type FromStrRadixErr = String;
fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
if let Some((num_str, den_str)) = str.split_once('/') {
let num = BigInt::from_str_radix(num_str.trim(), radix)
.map_err(|e| format!("invalid numerator: {}", e))?;
let den = BigInt::from_str_radix(den_str.trim(), radix)
.map_err(|e| format!("invalid denominator: {}", e))?;
if den.is_zero() {
return Err("denominator is zero".to_string());
}
Ok(FastRational::from_big(BigRational::new(num, den)))
} else {
let num = BigInt::from_str_radix(str.trim(), radix)
.map_err(|e| format!("invalid integer: {}", e))?;
Ok(FastRational::from_bigint(&num))
}
}
}
impl Neg for FastRational {
type Output = FastRational;
#[inline]
fn neg(self) -> Self::Output {
match self {
FastRational::Small { num, den } => match num.checked_neg() {
Some(n) => FastRational::Small { num: n, den },
None => {
let big = BigRational::new(-BigInt::from(num), BigInt::from(den));
FastRational::Big(Box::new(big))
}
},
FastRational::Big(b) => FastRational::from_big(-*b),
}
}
}
impl Neg for &FastRational {
type Output = FastRational;
#[inline]
fn neg(self) -> Self::Output {
match self {
FastRational::Small { num, den } => match num.checked_neg() {
Some(n) => FastRational::Small { num: n, den: *den },
None => {
let big = BigRational::new(-BigInt::from(*num), BigInt::from(*den));
FastRational::Big(Box::new(big))
}
},
FastRational::Big(b) => FastRational::from_big(-((**b).clone())),
}
}
}
impl Add for FastRational {
type Output = FastRational;
#[inline]
fn add(self, rhs: FastRational) -> Self::Output {
(&self).add(&rhs)
}
}
impl Add<&FastRational> for FastRational {
type Output = FastRational;
#[inline]
fn add(self, rhs: &FastRational) -> Self::Output {
(&self).add(rhs)
}
}
impl Add<FastRational> for &FastRational {
type Output = FastRational;
#[inline]
fn add(self, rhs: FastRational) -> Self::Output {
self.add(&rhs)
}
}
impl Add<&FastRational> for &FastRational {
type Output = FastRational;
#[inline]
fn add(self, rhs: &FastRational) -> Self::Output {
match (self, rhs) {
(
FastRational::Small { num: an, den: ad },
FastRational::Small { num: bn, den: bd },
) => add_small(*an, *ad, *bn, *bd),
(a, b) => {
let big_a = a.to_big_rational();
let big_b = b.to_big_rational();
FastRational::from_big(big_a + big_b)
}
}
}
}
impl AddAssign for FastRational {
#[inline]
fn add_assign(&mut self, rhs: FastRational) {
*self = (&*self) + &rhs;
}
}
impl AddAssign<&FastRational> for FastRational {
#[inline]
fn add_assign(&mut self, rhs: &FastRational) {
*self = (&*self) + rhs;
}
}
impl Sub for FastRational {
type Output = FastRational;
#[inline]
fn sub(self, rhs: FastRational) -> Self::Output {
(&self).sub(&rhs)
}
}
impl Sub<&FastRational> for FastRational {
type Output = FastRational;
#[inline]
fn sub(self, rhs: &FastRational) -> Self::Output {
(&self).sub(rhs)
}
}
impl Sub<FastRational> for &FastRational {
type Output = FastRational;
#[inline]
fn sub(self, rhs: FastRational) -> Self::Output {
self.sub(&rhs)
}
}
impl Sub<&FastRational> for &FastRational {
type Output = FastRational;
#[inline]
fn sub(self, rhs: &FastRational) -> Self::Output {
match (self, rhs) {
(
FastRational::Small { num: an, den: ad },
FastRational::Small { num: bn, den: bd },
) => sub_small(*an, *ad, *bn, *bd),
(a, b) => {
let big_a = a.to_big_rational();
let big_b = b.to_big_rational();
FastRational::from_big(big_a - big_b)
}
}
}
}
impl SubAssign for FastRational {
#[inline]
fn sub_assign(&mut self, rhs: FastRational) {
*self = (&*self) - &rhs;
}
}
impl SubAssign<&FastRational> for FastRational {
#[inline]
fn sub_assign(&mut self, rhs: &FastRational) {
*self = (&*self) - rhs;
}
}
impl Mul for FastRational {
type Output = FastRational;
#[inline]
fn mul(self, rhs: FastRational) -> Self::Output {
(&self).mul(&rhs)
}
}
impl Mul<&FastRational> for FastRational {
type Output = FastRational;
#[inline]
fn mul(self, rhs: &FastRational) -> Self::Output {
(&self).mul(rhs)
}
}
impl Mul<FastRational> for &FastRational {
type Output = FastRational;
#[inline]
fn mul(self, rhs: FastRational) -> Self::Output {
self.mul(&rhs)
}
}
impl Mul<&FastRational> for &FastRational {
type Output = FastRational;
#[inline]
fn mul(self, rhs: &FastRational) -> Self::Output {
match (self, rhs) {
(
FastRational::Small { num: an, den: ad },
FastRational::Small { num: bn, den: bd },
) => mul_small(*an, *ad, *bn, *bd),
(a, b) => {
let big_a = a.to_big_rational();
let big_b = b.to_big_rational();
FastRational::from_big(big_a * big_b)
}
}
}
}
impl MulAssign for FastRational {
#[inline]
fn mul_assign(&mut self, rhs: FastRational) {
*self = (&*self) * &rhs;
}
}
impl MulAssign<&FastRational> for FastRational {
#[inline]
fn mul_assign(&mut self, rhs: &FastRational) {
*self = (&*self) * rhs;
}
}
impl Div for FastRational {
type Output = FastRational;
#[inline]
fn div(self, rhs: FastRational) -> Self::Output {
(&self).div(&rhs)
}
}
impl Div<&FastRational> for FastRational {
type Output = FastRational;
#[inline]
fn div(self, rhs: &FastRational) -> Self::Output {
(&self).div(rhs)
}
}
impl Div<FastRational> for &FastRational {
type Output = FastRational;
#[inline]
fn div(self, rhs: FastRational) -> Self::Output {
self.div(&rhs)
}
}
impl Div<&FastRational> for &FastRational {
type Output = FastRational;
#[inline]
fn div(self, rhs: &FastRational) -> Self::Output {
match (self, rhs) {
(
FastRational::Small { num: an, den: ad },
FastRational::Small { num: bn, den: bd },
) => match div_small(*an, *ad, *bn, *bd) {
Some(r) => r,
None => panic!("FastRational: division by zero"),
},
(a, b) => {
if b.is_zero() {
panic!("FastRational: division by zero");
}
let big_a = a.to_big_rational();
let big_b = b.to_big_rational();
FastRational::from_big(big_a / big_b)
}
}
}
}
impl DivAssign for FastRational {
#[inline]
fn div_assign(&mut self, rhs: FastRational) {
*self = (&*self) / &rhs;
}
}
impl DivAssign<&FastRational> for FastRational {
#[inline]
fn div_assign(&mut self, rhs: &FastRational) {
*self = (&*self) / rhs;
}
}
impl Rem for FastRational {
type Output = FastRational;
#[inline]
fn rem(self, rhs: FastRational) -> Self::Output {
(&self).rem(&rhs)
}
}
impl Rem<&FastRational> for &FastRational {
type Output = FastRational;
#[inline]
fn rem(self, rhs: &FastRational) -> Self::Output {
if rhs.is_zero() {
return FastRational::zero();
}
let quotient = self / rhs;
let floor_q = FastRational::from_integer(quotient.floor());
self - &(rhs * &floor_q)
}
}
impl RemAssign for FastRational {
#[inline]
fn rem_assign(&mut self, rhs: FastRational) {
*self = (&*self).rem(&rhs);
}
}
impl PartialEq for FastRational {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(
FastRational::Small { num: an, den: ad },
FastRational::Small { num: bn, den: bd },
) => {
an == bn && ad == bd
}
(a, b) => {
a.to_big_rational() == b.to_big_rational()
}
}
}
}
impl Eq for FastRational {}
impl PartialOrd for FastRational {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for FastRational {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(
FastRational::Small { num: an, den: ad },
FastRational::Small { num: bn, den: bd },
) => {
if let (Some(lhs), Some(rhs)) = (an.checked_mul(*bd), bn.checked_mul(*ad)) {
return lhs.cmp(&rhs);
}
let big_a = BigRational::new(BigInt::from(*an), BigInt::from(*ad));
let big_b = BigRational::new(BigInt::from(*bn), BigInt::from(*bd));
big_a.cmp(&big_b)
}
(a, b) => {
let big_a = a.to_big_rational();
let big_b = b.to_big_rational();
big_a.cmp(&big_b)
}
}
}
}
impl Hash for FastRational {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
FastRational::Small { num, den } => {
num.hash(state);
den.hash(state);
}
FastRational::Big(b) => {
let reduced = b.reduced();
let n_opt: Option<i64> = reduced.numer().try_into().ok();
let d_opt: Option<i64> = reduced.denom().try_into().ok();
match (n_opt, d_opt) {
(Some(n), Some(d)) if d > 0 => {
n.hash(state);
d.hash(state);
}
(Some(n), Some(d)) if d < 0 => {
(-n).hash(state);
(-d).hash(state);
}
_ => {
let (n, d) = if reduced.denom().is_negative() {
(-reduced.numer(), -reduced.denom())
} else {
(reduced.numer().clone(), reduced.denom().clone())
};
n.hash(state);
d.hash(state);
}
}
}
}
}
}
impl fmt::Display for FastRational {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FastRational::Small { num, den } => {
if *den == 1 {
write!(f, "{}", num)
} else {
write!(f, "{}/{}", num, den)
}
}
FastRational::Big(b) => write!(f, "{}", b),
}
}
}
impl FastRational {
#[inline]
pub fn from_integer(n: BigInt) -> Self {
FastRational::from_bigint(&n)
}
}
impl FastRational {
#[inline]
pub fn max(self, other: Self) -> Self {
if self >= other { self } else { other }
}
#[inline]
pub fn min(self, other: Self) -> Self {
if self <= other { self } else { other }
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::hash_map::DefaultHasher;
fn small(n: i64, d: i64) -> FastRational {
FastRational::new_small(n, d)
}
fn fr(n: i64) -> FastRational {
FastRational::from(n)
}
fn hash_of(val: &FastRational) -> u64 {
let mut h = DefaultHasher::new();
val.hash(&mut h);
h.finish()
}
#[test]
fn test_new_small_normalization() {
let r = small(2, 4);
assert_eq!(r, small(1, 2));
}
#[test]
fn test_new_small_negative_den() {
let r = small(3, -5);
assert_eq!(r, small(-3, 5));
}
#[test]
fn test_new_small_zero() {
let r = small(0, 42);
assert_eq!(r, FastRational::zero());
}
#[test]
fn test_from_i64() {
let r = FastRational::from(7i64);
assert_eq!(r, small(7, 1));
}
#[test]
fn test_from_tuple() {
let r = FastRational::from((6i64, 4i64));
assert_eq!(r, small(3, 2));
}
#[test]
fn test_from_bigrational() {
let br = BigRational::new(BigInt::from(10), BigInt::from(4));
let r = FastRational::from(br);
assert_eq!(r, small(5, 2));
}
#[test]
fn test_add() {
let a = small(1, 2);
let b = small(1, 3);
assert_eq!(&a + &b, small(5, 6));
}
#[test]
fn test_sub() {
let a = small(3, 4);
let b = small(1, 4);
assert_eq!(&a - &b, small(1, 2));
}
#[test]
fn test_mul() {
let a = small(2, 3);
let b = small(3, 4);
assert_eq!(&a * &b, small(1, 2));
}
#[test]
fn test_div() {
let a = small(2, 3);
let b = small(4, 5);
assert_eq!(&a / &b, small(5, 6));
}
#[test]
fn test_neg() {
let r = small(3, 5);
assert_eq!(-r, small(-3, 5));
}
#[test]
fn test_add_assign() {
let mut a = small(1, 2);
a += small(1, 3);
assert_eq!(a, small(5, 6));
}
#[test]
fn test_mul_assign() {
let mut a = small(2, 3);
a *= small(3, 4);
assert_eq!(a, small(1, 2));
}
#[test]
fn test_overflow_to_big() {
let big_val = i64::MAX;
let a = fr(big_val);
let b = fr(big_val);
let result = &a + &b;
let expected = BigRational::from_integer(BigInt::from(big_val))
+ BigRational::from_integer(BigInt::from(big_val));
assert_eq!(result.to_big_rational(), expected);
}
#[test]
fn test_ord() {
assert!(small(1, 3) < small(1, 2));
assert!(small(-1, 2) < small(1, 2));
assert!(small(0, 1) == FastRational::zero());
}
#[test]
fn test_eq_cross_representation() {
let s = small(1, 2);
let b = FastRational::Big(Box::new(BigRational::new(BigInt::from(1), BigInt::from(2))));
assert_eq!(s, b);
}
#[test]
fn test_hash_consistency() {
let s = small(1, 2);
let b = FastRational::Big(Box::new(BigRational::new(BigInt::from(1), BigInt::from(2))));
assert_eq!(hash_of(&s), hash_of(&b));
}
#[test]
fn test_zero_one() {
assert!(FastRational::zero().is_zero());
assert!(FastRational::one().is_one());
assert!(!FastRational::zero().is_one());
assert!(!FastRational::one().is_zero());
}
#[test]
fn test_signed() {
assert!(small(3, 5).is_positive());
assert!(small(-3, 5).is_negative());
assert!(!FastRational::zero().is_positive());
assert!(!FastRational::zero().is_negative());
}
#[test]
fn test_abs() {
assert_eq!(small(-3, 5).abs(), small(3, 5));
assert_eq!(small(3, 5).abs(), small(3, 5));
}
#[test]
fn test_recip() {
assert_eq!(small(3, 5).recip(), Some(small(5, 3)));
assert_eq!(FastRational::zero().recip(), None);
}
#[test]
fn test_floor_ceil() {
let r = small(7, 3);
assert_eq!(r.floor(), BigInt::from(2));
assert_eq!(r.ceil(), BigInt::from(3));
let r = small(-7, 3);
assert_eq!(r.floor(), BigInt::from(-3));
assert_eq!(r.ceil(), BigInt::from(-2));
}
#[test]
fn test_ceil_i64_max_boundary_no_overflow() {
let r = FastRational::new_small(i64::MAX, 2);
let big = r.to_big_rational();
assert_eq!(r.ceil(), crate::rational::ceil(&big));
assert_eq!(r.floor(), crate::rational::floor(&big));
assert_eq!(r.ceil(), r.floor() + BigInt::from(1));
}
#[test]
fn test_ceil_i64_min_boundary() {
let r = FastRational::new_small(i64::MIN, 2);
assert_eq!(r.ceil(), BigInt::from(i64::MIN / 2));
assert_eq!(r.floor(), BigInt::from(i64::MIN / 2));
}
#[test]
fn test_ceil_large_positive_non_integer() {
let r = FastRational::new_small(i64::MAX - 1, 4);
let ceil = r.ceil();
let floor = r.floor();
let big = r.to_big_rational();
let expected_floor = crate::rational::floor(&big);
let expected_ceil = crate::rational::ceil(&big);
assert_eq!(floor, expected_floor);
assert_eq!(ceil, expected_ceil);
}
#[test]
fn test_to_f64() {
let r = small(1, 2);
assert!((r.to_f64() - 0.5).abs() < 1e-10);
}
#[test]
fn test_to_big_rational_roundtrip() {
let r = small(7, 13);
let big = r.to_big_rational();
let back = FastRational::from_big(big);
assert_eq!(r, back);
}
#[test]
fn test_numer_denom() {
let r = small(3, 7);
assert_eq!(r.numer(), BigInt::from(3));
assert_eq!(r.denom(), BigInt::from(7));
}
#[test]
fn test_is_integer() {
assert!(fr(5).is_integer());
assert!(!small(5, 3).is_integer());
}
#[test]
fn test_display() {
assert_eq!(format!("{}", fr(5)), "5");
assert_eq!(format!("{}", small(3, 7)), "3/7");
assert_eq!(format!("{}", small(-1, 2)), "-1/2");
}
#[test]
fn test_max_min() {
let a = small(1, 3);
let b = small(1, 2);
assert_eq!(a.clone().max(b.clone()), b);
assert_eq!(a.clone().min(b.clone()), a);
}
#[test]
fn test_new_small_i64_min_odd_denominator_stays_irreducible() {
let r = FastRational::new_small(i64::MIN, 7);
assert_eq!(
r,
FastRational::Small {
num: i64::MIN,
den: 7
}
);
let expected = BigRational::new(BigInt::from(i64::MIN), BigInt::from(7));
assert_eq!(r.to_big_rational(), expected);
}
#[test]
fn test_new_small_i64_min_even_denominator_reduces_correctly() {
let r = FastRational::new_small(i64::MIN, 2);
assert_eq!(
r,
FastRational::Small {
num: i64::MIN / 2,
den: 1
}
);
let expected = BigRational::new(BigInt::from(i64::MIN), BigInt::from(2));
assert_eq!(r.to_big_rational(), expected);
}
#[test]
fn test_mul_small_i64_min_by_small_fraction() {
let a = FastRational::Small {
num: i64::MIN,
den: 1,
};
let b = FastRational::Small { num: 1, den: 7 };
let result = &a * &b;
let expected = BigRational::new(BigInt::from(i64::MIN), BigInt::from(7));
assert_eq!(
result.to_big_rational(),
expected,
"MIN * (1/7) must equal MIN/7 exactly, not truncate to an integer"
);
assert!(!result.is_integer());
}
#[test]
fn test_mul_small_i64_min_eq_hash_invariant_preserved() {
let a = FastRational::Small {
num: i64::MIN,
den: 1,
};
let b = FastRational::Small { num: 1, den: 7 };
let small_result = &a * &b;
let big_a = BigRational::from_integer(BigInt::from(i64::MIN));
let big_b = BigRational::new(BigInt::one(), BigInt::from(7));
let big_result = FastRational::from_big(big_a * big_b);
assert_eq!(small_result, big_result);
assert_eq!(hash_of(&small_result), hash_of(&big_result));
}
#[test]
fn test_from_str_radix() {
use num_traits::Num;
let r = FastRational::from_str_radix("3/7", 10);
assert!(r.is_ok());
assert_eq!(r.ok(), Some(small(3, 7)));
let r2 = FastRational::from_str_radix("42", 10);
assert!(r2.is_ok());
assert_eq!(r2.ok(), Some(fr(42)));
}
}