use core::cmp::Ordering;
use core::fmt;
use crate::error::{Error, Result};
use crate::int::{Int, Sign};
use crate::nat::Nat;
#[derive(Clone, PartialEq, Eq)]
pub struct Rational {
num: Int,
den: Nat,
}
impl Rational {
#[inline]
pub fn zero() -> Self {
Rational {
num: Int::zero(),
den: Nat::one(),
}
}
#[inline]
pub fn one() -> Self {
Rational {
num: Int::one(),
den: Nat::one(),
}
}
pub fn new(num: Int, den: Int) -> Result<Self> {
if den.is_zero() {
return Err(Error::DivisionByZero);
}
let sign = match (num.sign(), den.sign()) {
(Sign::Zero, _) => Sign::Zero,
(a, b) if a == b => Sign::Positive,
_ => Sign::Negative,
};
let mut num_mag = num.magnitude().clone();
let mut den_mag = den.magnitude().clone();
let g = num_mag.gcd(&den_mag);
if !g.is_zero() && g != Nat::one() {
num_mag = num_mag.div_rem(&g).expect("gcd divides numerator").0;
den_mag = den_mag.div_rem(&g).expect("gcd divides denominator").0;
}
Ok(Rational {
num: Int::from_sign_magnitude(sign, num_mag),
den: den_mag,
})
}
pub fn from_int(n: Int) -> Self {
Rational {
num: n,
den: Nat::one(),
}
}
#[inline]
pub fn numerator(&self) -> &Int {
&self.num
}
#[inline]
pub fn denominator(&self) -> &Nat {
&self.den
}
#[inline]
pub fn is_zero(&self) -> bool {
self.num.is_zero()
}
#[inline]
pub fn is_integer(&self) -> bool {
self.den == Nat::one()
}
pub fn neg(&self) -> Rational {
Rational {
num: self.num.neg(),
den: self.den.clone(),
}
}
pub fn recip(&self) -> Result<Rational> {
Rational::new(Int::from(self.den.clone()), self.num.clone())
}
pub fn add(&self, rhs: &Rational) -> Rational {
let ad = self.num.mul(&Int::from(rhs.den.clone()));
let cb = rhs.num.mul(&Int::from(self.den.clone()));
let num = ad.add(&cb);
let den = Int::from(self.den.mul(&rhs.den));
Rational::new(num, den).expect("product of positive denominators is non-zero")
}
pub fn sub(&self, rhs: &Rational) -> Rational {
self.add(&rhs.neg())
}
pub fn mul(&self, rhs: &Rational) -> Rational {
let num = self.num.mul(&rhs.num);
let den = Int::from(self.den.mul(&rhs.den));
Rational::new(num, den).expect("product of positive denominators is non-zero")
}
pub fn div(&self, rhs: &Rational) -> Result<Rational> {
let num = self.num.mul(&Int::from(rhs.den.clone()));
let den = Int::from(self.den.clone()).mul(&rhs.num);
Rational::new(num, den)
}
}
impl PartialOrd for Rational {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Rational {
fn cmp(&self, other: &Self) -> Ordering {
let lhs = self.num.mul(&Int::from(other.den.clone()));
let rhs = other.num.mul(&Int::from(self.den.clone()));
lhs.cmp(&rhs)
}
}
impl From<Int> for Rational {
#[inline]
fn from(n: Int) -> Self {
Rational::from_int(n)
}
}
impl fmt::Display for Rational {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_integer() {
fmt::Display::fmt(&self.num, f)
} else {
write!(f, "{}/{}", self.num, self.den)
}
}
}
impl fmt::Debug for Rational {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Rational({self})")
}
}