use super::integer::{Integer, big_gcd, big_sign, gcd_u64};
use num_bigint::BigInt;
use std::cmp::Ordering;
use std::fmt;
use std::hash::{Hash, Hasher};
#[derive(Clone, Debug, PartialEq, Eq)]
enum Repr {
Small { n: i64, d: u64 },
Big(Box<(BigInt, BigInt)>),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Rational(Repr);
impl Rational {
pub fn zero() -> Self {
Rational(Repr::Small { n: 0, d: 1 })
}
pub fn one() -> Self {
Rational(Repr::Small { n: 1, d: 1 })
}
pub fn from_integer(v: &Integer) -> Self {
match v.to_i64() {
Some(n) => Rational(Repr::Small { n, d: 1 }),
None => Rational(Repr::Big(Box::new((v.to_bigint(), BigInt::from(1))))),
}
}
pub fn from_ints(n: &Integer, d: &Integer) -> Option<Self> {
if d.is_zero() {
return None;
}
let (n, d) = if d.is_negative() {
(n.neg(), d.abs())
} else {
(n.clone(), d.clone())
};
if n.is_zero() {
return Some(Rational::zero());
}
let normalized = match (n.to_i64(), d.to_i64()) {
(Some(sn), Some(sd)) => {
let sd = sd as u64;
let g = gcd_u64(sn.unsigned_abs(), sd);
let nn = (sn as i128 / g as i128) as i64;
Rational(Repr::Small { n: nn, d: sd / g })
}
_ => Self::from_big_parts(n.to_bigint(), d.to_bigint()),
};
Some(normalized)
}
pub fn is_zero(&self) -> bool {
matches!(self.0, Repr::Small { n: 0, .. })
}
pub fn is_one(&self) -> bool {
matches!(self.0, Repr::Small { n: 1, d: 1 })
}
pub fn is_negative(&self) -> bool {
self.sign() < 0
}
pub fn sign(&self) -> i32 {
match &self.0 {
Repr::Small { n, .. } => n.signum() as i32,
Repr::Big(p) => match big_sign(&p.0) {
Ordering::Less => -1,
Ordering::Equal => 0,
Ordering::Greater => 1,
},
}
}
pub fn num(&self) -> Integer {
match &self.0 {
Repr::Small { n, .. } => Integer::from_i64(*n),
Repr::Big(p) => Integer::from_bigint(p.0.clone()),
}
}
pub fn den(&self) -> Integer {
match &self.0 {
Repr::Small { d, .. } => Integer::from_u64(*d),
Repr::Big(p) => Integer::from_bigint(p.1.clone()),
}
}
pub fn neg(&self) -> Self {
match &self.0 {
Repr::Small { n, d } => Rational(Repr::Small { n: -n, d: *d }),
Repr::Big(p) => Self::from_big_parts(-p.0.clone(), p.1.clone()),
}
}
pub fn abs(&self) -> Self {
if self.is_negative() {
self.neg()
} else {
self.clone()
}
}
pub fn inv(&self) -> Option<Self> {
Self::from_ints(&self.den(), &self.num())
}
pub fn add(&self, other: &Self) -> Self {
match (&self.0, &other.0) {
(Repr::Small { n: n1, d: d1 }, Repr::Small { n: n2, d: d2 }) => {
let n = (*n1 as i128) * (*d2 as i128) + (*n2 as i128) * (*d1 as i128);
let d = (*d1 as u128) * (*d2 as u128);
Self::from_parts_wide(n, d)
}
_ => Self::from_big_parts(
self.n_big() * other.d_big() + other.n_big() * self.d_big(),
self.d_big() * other.d_big(),
),
}
}
pub fn sub(&self, other: &Self) -> Self {
self.add(&other.neg())
}
pub fn mul(&self, other: &Self) -> Self {
match (&self.0, &other.0) {
(Repr::Small { n: n1, d: d1 }, Repr::Small { n: n2, d: d2 }) => {
let n = (*n1 as i128) * (*n2 as i128);
let d = (*d1 as u128) * (*d2 as u128);
Self::from_parts_wide(n, d)
}
_ => Self::from_big_parts(self.n_big() * other.n_big(), self.d_big() * other.d_big()),
}
}
pub fn div(&self, other: &Self) -> Option<Self> {
Some(self.mul(&other.inv()?))
}
pub fn pow(&self, exp: u32) -> Self {
Self::from_ints(&self.num().pow(exp), &self.den().pow(exp)).expect("分母非零")
}
pub fn pow_reduced(&self, exp: u32) -> Self {
let n = self.num().pow(exp);
let d = self.den().pow(exp);
match (n.to_i64(), d.to_u64()) {
(Some(sn), Some(sd)) => Rational(Repr::Small { n: sn, d: sd }),
_ => Rational(Repr::Big(Box::new((n.to_bigint(), d.to_bigint())))),
}
}
pub fn to_f64(&self) -> f64 {
match &self.0 {
Repr::Small { n, d } => (*n as f64) / (*d as f64),
Repr::Big(p) => {
let n: f64 = p.0.to_string().parse().unwrap_or(f64::INFINITY);
let d: f64 = p.1.to_string().parse().unwrap_or(f64::INFINITY);
n / d
}
}
}
pub fn inv_reduced(&self) -> Option<Self> {
match &self.0 {
Repr::Small { n: 0, .. } => None,
Repr::Small { n, d } => {
if *d <= i64::MAX as u64 {
let num = *d as i64;
Some(Rational(Repr::Small {
n: if *n < 0 { -num } else { num },
d: n.unsigned_abs(),
}))
} else {
let num = Integer::from_u64(*d);
let num = if *n < 0 { num.neg() } else { num };
Some(Rational(Repr::Big(Box::new((
num.to_bigint(),
BigInt::from(n.unsigned_abs()),
)))))
}
}
Repr::Big(p) => {
let (num, den) = if p.0.sign() == num_bigint::Sign::Minus {
(-p.1.clone(), -p.0.clone())
} else {
(p.1.clone(), p.0.clone())
};
Some(Rational(Repr::Big(Box::new((num, den)))))
}
}
}
pub fn cmp_integer(&self, o: &Integer) -> Ordering {
match (&self.0, o.to_i64()) {
(Repr::Small { n, d }, Some(i)) => (*n as i128).cmp(&((i as i128) * (*d as i128))),
_ => self.n_big().cmp(&(o.to_bigint() * self.d_big())),
}
}
fn from_parts_wide(n: i128, d: u128) -> Self {
debug_assert!(d > 0);
if n == 0 {
return Rational::zero();
}
let g = gcd_u128(n.unsigned_abs(), d);
let (n, d) = (n / g as i128, d / g);
match (i64::try_from(n), u64::try_from(d)) {
(Ok(sn), Ok(sd)) => Rational(Repr::Small { n: sn, d: sd }),
_ => Self::from_big_parts(BigInt::from(n), BigInt::from(d)),
}
}
fn from_big_parts(n: BigInt, d: BigInt) -> Self {
debug_assert!(d.sign() != num_bigint::Sign::Minus);
let g = big_gcd(&n, &d);
let (n, d) = if g == BigInt::from(1) {
(n, d)
} else {
(n / &g, d / &g)
};
match (i64::try_from(&n), u64::try_from(&d)) {
(Ok(sn), Ok(sd)) => Rational(Repr::Small { n: sn, d: sd }),
_ => Rational(Repr::Big(Box::new((n, d)))),
}
}
fn n_big(&self) -> BigInt {
match &self.0 {
Repr::Small { n, .. } => BigInt::from(*n),
Repr::Big(p) => p.0.clone(),
}
}
fn d_big(&self) -> BigInt {
match &self.0 {
Repr::Small { d, .. } => BigInt::from(*d),
Repr::Big(p) => p.1.clone(),
}
}
}
fn gcd_u128(mut a: u128, mut b: u128) -> u128 {
while b != 0 {
let r = a % b;
a = b;
b = r;
}
a
}
impl Ord for Rational {
fn cmp(&self, other: &Self) -> Ordering {
match (&self.0, &other.0) {
(Repr::Small { n: n1, d: d1 }, Repr::Small { n: n2, d: d2 }) => {
let l = (*n1 as i128) * (*d2 as i128);
let r = (*n2 as i128) * (*d1 as i128);
l.cmp(&r)
}
_ => (self.n_big() * other.d_big()).cmp(&(other.n_big() * self.d_big())),
}
}
}
impl PartialOrd for Rational {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Hash for Rational {
fn hash<H: Hasher>(&self, state: &mut H) {
match &self.0 {
Repr::Small { n, d } => {
n.hash(state);
d.hash(state);
}
Repr::Big(p) => {
p.0.hash(state);
p.1.hash(state);
}
}
}
}
impl fmt::Display for Rational {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
Repr::Small { n, d: 1 } => write!(f, "{n}"),
Repr::Small { n, d } => write!(f, "{n}/{d}"),
Repr::Big(p) if p.1 == BigInt::from(1) => write!(f, "{}", p.0),
Repr::Big(p) => write!(f, "{}/{}", p.0, p.1),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn r(n: i64, d: i64) -> Rational {
Rational::from_ints(&Integer::from_i64(n), &Integer::from_i64(d)).unwrap()
}
#[test]
fn 规范化() {
assert_eq!(r(4, 8), r(1, 2));
assert_eq!(r(-6, 4), r(-3, 2));
assert_eq!(r(6, -4), r(-3, 2));
assert_eq!(r(0, 7), Rational::zero());
assert_eq!(r(9, 3).to_string(), "3");
assert_eq!(r(7, 3).to_string(), "7/3");
assert_eq!(
Rational::from_ints(&Integer::from_i64(1), &Integer::zero()),
None
);
}
#[test]
fn 四则() {
assert_eq!(r(1, 2).add(&r(1, 3)), r(5, 6));
assert_eq!(r(1, 2).sub(&r(1, 3)), r(1, 6));
assert_eq!(r(2, 3).mul(&r(3, 4)), r(1, 2));
assert_eq!(r(1, 2).div(&r(3, 4)), Some(r(2, 3)));
assert_eq!(r(1, 2).div(&Rational::zero()), None);
assert_eq!(r(1, 3).mul(&r(3, 1)), Rational::one());
}
#[test]
fn 大数路径() {
let big = Integer::parse("9223372036854775808").unwrap(); let q = Rational::from_ints(&big, &Integer::from_i64(2)).unwrap();
assert_eq!(q.to_string(), "4611686018427387904");
assert!(q.cmp(&r(1, 1)) == Ordering::Greater);
assert_eq!(q.mul(&r(1, 2)).to_string(), "2305843009213693952");
let m = Integer::from_i64(i64::MIN);
let qm = Rational::from_ints(&m, &Integer::from_i64(2)).unwrap();
assert_eq!(qm.to_string(), "-4611686018427387904");
}
#[test]
fn 幂与逆元() {
assert_eq!(r(2, 3).pow(3), r(8, 27));
assert_eq!(r(3, 2).inv(), Some(r(2, 3)));
assert_eq!(r(-3, 2).inv(), Some(r(-2, 3)));
assert_eq!(Rational::zero().inv(), None);
}
#[test]
fn 与整数比较() {
assert_eq!(
r(5, 2).cmp_integer(&Integer::from_i64(2)),
Ordering::Greater
);
assert_eq!(r(5, 2).cmp_integer(&Integer::from_i64(3)), Ordering::Less);
assert_eq!(r(4, 2).cmp_integer(&Integer::from_i64(2)), Ordering::Equal);
let big = Integer::parse("99999999999999999999").unwrap();
assert_eq!(r(1, 1).cmp_integer(&big), Ordering::Less);
}
#[test]
fn 快速路径落位一致性() {
let d13 = Integer::parse("10260628712958602189").unwrap();
let r = Rational::from_ints(&Integer::from_i64(-2), &Integer::from_i64(29)).unwrap();
let p = r.pow_reduced(13);
let expect = Rational::from_ints(&Integer::from_i64(-8192), &d13).unwrap();
assert_eq!(p, expect); assert_eq!(p.to_string(), "-8192/10260628712958602189");
let q = Rational::from_ints(&Integer::from_i64(3), &d13).unwrap();
let inv = q.inv_reduced().unwrap();
let expect_inv = Rational::from_ints(&d13, &Integer::from_i64(3)).unwrap();
assert_eq!(inv, expect_inv);
}
}