#![allow(clippy::should_implement_trait)]
use crate::tern::Tern;
use crate::trit::{Trit, N};
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct Rat {
num: Tern,
den: Tern,
}
impl Rat {
pub fn new(num: Tern, den: Tern) -> Rat {
assert!(!den.is_zero(), "Rat: zero denominator");
if num.is_zero() {
return Rat {
num: Tern::ZERO,
den: Tern::ONE,
};
}
let g = Tern::gcd(num, den);
let (mut n, _) = num.div_rem(g);
let (mut d, _) = den.div_rem(g);
if d.sign() == N {
n = n.neg();
d = d.neg();
}
Rat { num: n, den: d }
}
pub fn from_i64(n: i64, d: i64) -> Rat {
Rat::new(Tern::from_i64(n), Tern::from_i64(d))
}
pub fn integer(n: i64) -> Rat {
Rat {
num: Tern::from_i64(n),
den: Tern::ONE,
}
}
pub fn zero() -> Rat {
Rat::integer(0)
}
pub fn one() -> Rat {
Rat::integer(1)
}
pub fn neg_one() -> Rat {
Rat::integer(-1)
}
pub fn is_zero(&self) -> bool {
self.num.is_zero()
}
pub fn is_one(&self) -> bool {
self.num == self.den
}
pub fn numerator(&self) -> Tern {
self.num
}
pub fn denominator(&self) -> Tern {
self.den
}
pub fn sign(&self) -> Trit {
self.num.sign()
}
pub fn neg(self) -> Rat {
Rat {
num: self.num.neg(),
den: self.den,
}
}
pub fn abs(self) -> Rat {
Rat {
num: self.num.abs(),
den: self.den,
}
}
pub fn add(self, rhs: Rat) -> Rat {
let num = self.num.mul(rhs.den).add(rhs.num.mul(self.den));
let den = self.den.mul(rhs.den);
Rat::new(num, den)
}
pub fn sub(self, rhs: Rat) -> Rat {
self.add(rhs.neg())
}
pub fn mul(self, rhs: Rat) -> Rat {
Rat::new(self.num.mul(rhs.num), self.den.mul(rhs.den))
}
pub fn div(self, rhs: Rat) -> Rat {
assert!(!rhs.is_zero(), "Rat: division by zero");
self.mul(rhs.recip())
}
pub fn recip(self) -> Rat {
assert!(!self.is_zero(), "Rat: reciprocal of zero");
Rat::new(self.den, self.num)
}
pub fn to_f64(self) -> f64 {
self.num.to_i64() as f64 / self.den.to_i64() as f64
}
}
impl PartialOrd for Rat {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Rat {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let lhs = self.num.mul(other.den);
let rhs = other.num.mul(self.den);
lhs.cmp(&rhs)
}
}
impl std::fmt::Debug for Rat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.den == Tern::ONE {
write!(f, "Rat({})", self.num)
} else {
write!(f, "Rat({}/{})", self.num, self.den)
}
}
}
impl std::fmt::Display for Rat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.den == Tern::ONE {
write!(f, "{}", self.num)
} else {
write!(f, "{}/{}", self.num, self.den)
}
}
}
impl std::ops::Add for Rat {
type Output = Rat;
fn add(self, r: Rat) -> Rat {
Rat::add(self, r)
}
}
impl std::ops::Sub for Rat {
type Output = Rat;
fn sub(self, r: Rat) -> Rat {
Rat::sub(self, r)
}
}
impl std::ops::Mul for Rat {
type Output = Rat;
fn mul(self, r: Rat) -> Rat {
Rat::mul(self, r)
}
}
impl std::ops::Neg for Rat {
type Output = Rat;
fn neg(self) -> Rat {
Rat::neg(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::trit::{N, P, Z};
fn r(n: i64, d: i64) -> Rat {
Rat::from_i64(n, d)
}
#[test]
fn reduction() {
let x = r(6, 4);
assert_eq!(x.numerator().to_i64(), 3);
assert_eq!(x.denominator().to_i64(), 2);
}
#[test]
fn negative_denominator_normalized() {
let x = r(3, -5);
assert_eq!(x.numerator().to_i64(), -3);
assert_eq!(x.denominator().to_i64(), 5);
}
#[test]
fn addition() {
let a = r(1, 2);
let b = r(1, 3);
let c = a + b;
assert_eq!(c.numerator().to_i64(), 5);
assert_eq!(c.denominator().to_i64(), 6);
}
#[test]
fn multiplication() {
assert_eq!(r(2, 3) * r(3, 4), r(1, 2));
}
#[test]
fn division() {
assert_eq!(r(2, 3).div(r(4, 5)), r(5, 6));
}
#[test]
fn negation() {
assert_eq!(-r(3, 7), r(-3, 7));
assert_eq!(-(-r(3, 7)), r(3, 7));
}
#[test]
fn ordering() {
assert!(r(1, 3) < r(1, 2));
assert!(r(-1, 2) < r(1, 2));
}
#[test]
fn sign() {
assert_eq!(r(-3, 5).sign(), N);
assert_eq!(Rat::zero().sign(), Z);
assert_eq!(r(7, 11).sign(), P);
}
#[test]
fn zero_arithmetic() {
let z = Rat::zero();
let x = r(5, 3);
assert_eq!(z + x, x);
assert_eq!(z * x, z);
}
}