#![allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_precision_loss,
clippy::cast_sign_loss
)]
use std::cmp::Ordering;
use std::fmt;
use std::ops::{Add, Div, Mul, Neg, Sub};
#[derive(Debug, Clone, Copy)]
pub struct Rational {
num: i32,
den: i32,
}
impl PartialEq for Rational {
fn eq(&self, other: &Self) -> bool {
i64::from(self.num) * i64::from(other.den) == i64::from(other.num) * i64::from(self.den)
}
}
impl Eq for Rational {}
impl std::hash::Hash for Rational {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
let reduced = self.reduce();
reduced.num.hash(state);
reduced.den.hash(state);
}
}
impl Rational {
#[must_use]
pub const fn new(num: i32, den: i32) -> Self {
if den < 0 {
Self {
num: -num,
den: -den,
}
} else {
Self { num, den }
}
}
#[must_use]
pub const fn zero() -> Self {
Self { num: 0, den: 1 }
}
#[must_use]
pub const fn one() -> Self {
Self { num: 1, den: 1 }
}
#[must_use]
#[inline]
pub const fn num(&self) -> i32 {
self.num
}
#[must_use]
#[inline]
pub const fn den(&self) -> i32 {
self.den
}
#[must_use]
#[inline]
pub fn as_f64(self) -> f64 {
if self.den == 0 {
match self.num.cmp(&0) {
Ordering::Greater => f64::INFINITY,
Ordering::Less => f64::NEG_INFINITY,
Ordering::Equal => f64::NAN,
}
} else {
f64::from(self.num) / f64::from(self.den)
}
}
#[must_use]
#[inline]
pub fn as_f32(self) -> f32 {
self.as_f64() as f32
}
#[must_use]
pub const fn invert(self) -> Self {
if self.num < 0 {
Self {
num: -self.den,
den: -self.num,
}
} else {
Self {
num: self.den,
den: self.num,
}
}
}
#[must_use]
#[inline]
pub const fn is_zero(self) -> bool {
self.num == 0
}
#[must_use]
#[inline]
pub const fn is_positive(self) -> bool {
self.num > 0 && self.den > 0
}
#[must_use]
#[inline]
pub const fn is_negative(self) -> bool {
self.num < 0 && self.den > 0
}
#[must_use]
pub const fn abs(self) -> Self {
Self {
num: if self.num < 0 { -self.num } else { self.num },
den: self.den,
}
}
#[must_use]
pub fn reduce(self) -> Self {
if self.num == 0 {
return Self::new(0, 1);
}
let g = gcd(self.num.unsigned_abs(), self.den.unsigned_abs());
Self {
num: self.num / g as i32,
den: self.den / g as i32,
}
}
}
fn gcd(mut a: u32, mut b: u32) -> u32 {
while b != 0 {
let temp = b;
b = a % b;
a = temp;
}
a
}
impl Default for Rational {
fn default() -> Self {
Self::one()
}
}
impl fmt::Display for Rational {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}/{}", self.num, self.den)
}
}
impl From<i32> for Rational {
fn from(n: i32) -> Self {
Self::new(n, 1)
}
}
impl From<(i32, i32)> for Rational {
fn from((num, den): (i32, i32)) -> Self {
Self::new(num, den)
}
}
impl Add for Rational {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
let num =
i64::from(self.num) * i64::from(rhs.den) + i64::from(rhs.num) * i64::from(self.den);
let den = i64::from(self.den) * i64::from(rhs.den);
let g = gcd(num.unsigned_abs() as u32, den.unsigned_abs() as u32);
Self::new((num / i64::from(g)) as i32, (den / i64::from(g)) as i32)
}
}
impl Sub for Rational {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
let num =
i64::from(self.num) * i64::from(rhs.den) - i64::from(rhs.num) * i64::from(self.den);
let den = i64::from(self.den) * i64::from(rhs.den);
let g = gcd(num.unsigned_abs() as u32, den.unsigned_abs() as u32);
Self::new((num / i64::from(g)) as i32, (den / i64::from(g)) as i32)
}
}
impl Mul for Rational {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
let num = i64::from(self.num) * i64::from(rhs.num);
let den = i64::from(self.den) * i64::from(rhs.den);
let g = gcd(num.unsigned_abs() as u32, den.unsigned_abs() as u32);
Self::new((num / i64::from(g)) as i32, (den / i64::from(g)) as i32)
}
}
impl Div for Rational {
type Output = Self;
#[allow(clippy::suspicious_arithmetic_impl)]
fn div(self, rhs: Self) -> Self::Output {
self * rhs.invert()
}
}
impl Mul<i32> for Rational {
type Output = Self;
fn mul(self, rhs: i32) -> Self::Output {
let num = i64::from(self.num) * i64::from(rhs);
let g = gcd(num.unsigned_abs() as u32, self.den.unsigned_abs());
Self::new((num / i64::from(g)) as i32, self.den / g as i32)
}
}
impl Div<i32> for Rational {
type Output = Self;
fn div(self, rhs: i32) -> Self::Output {
let den = i64::from(self.den) * i64::from(rhs);
let g = gcd(self.num.unsigned_abs(), den.unsigned_abs() as u32);
Self::new(self.num / g as i32, (den / i64::from(g)) as i32)
}
}
impl Neg for Rational {
type Output = Self;
fn neg(self) -> Self::Output {
Self::new(-self.num, self.den)
}
}
impl PartialOrd for Rational {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Rational {
fn cmp(&self, other: &Self) -> Ordering {
let left = i64::from(self.num) * i64::from(other.den);
let right = i64::from(other.num) * i64::from(self.den);
left.cmp(&right)
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::float_cmp,
clippy::similar_names,
clippy::redundant_closure_for_method_calls
)]
mod tests {
use super::*;
fn approx_eq(a: f64, b: f64) -> bool {
(a - b).abs() < 1e-9
}
mod rational_tests {
use super::*;
#[test]
fn test_new() {
let r = Rational::new(1, 2);
assert_eq!(r.num(), 1);
assert_eq!(r.den(), 2);
}
#[test]
fn test_new_negative_denominator() {
let r = Rational::new(1, -2);
assert_eq!(r.num(), -1);
assert_eq!(r.den(), 2);
let r = Rational::new(-1, -2);
assert_eq!(r.num(), 1);
assert_eq!(r.den(), 2);
}
#[test]
fn test_zero_and_one() {
let zero = Rational::zero();
assert!(zero.is_zero());
assert!(approx_eq(zero.as_f64(), 0.0));
let one = Rational::one();
assert!(approx_eq(one.as_f64(), 1.0));
assert!(!one.is_zero());
}
#[test]
fn test_as_f64() {
assert!(approx_eq(Rational::new(1, 2).as_f64(), 0.5));
assert!(approx_eq(Rational::new(1, 4).as_f64(), 0.25));
assert!((Rational::new(1, 3).as_f64() - 0.333_333).abs() < 0.001);
assert!(approx_eq(Rational::new(-1, 2).as_f64(), -0.5));
}
#[test]
fn test_as_f64_division_by_zero() {
assert!(Rational::new(1, 0).as_f64().is_infinite());
assert!(Rational::new(1, 0).as_f64().is_sign_positive());
assert!(Rational::new(-1, 0).as_f64().is_infinite());
assert!(Rational::new(-1, 0).as_f64().is_sign_negative());
assert!(Rational::new(0, 0).as_f64().is_nan());
}
#[test]
fn test_as_f32() {
assert_eq!(Rational::new(1, 2).as_f32(), 0.5);
}
#[test]
fn test_invert() {
let r = Rational::new(3, 4);
let inv = r.invert();
assert_eq!(inv.num(), 4);
assert_eq!(inv.den(), 3);
let r = Rational::new(-3, 4);
let inv = r.invert();
assert_eq!(inv.num(), -4);
assert_eq!(inv.den(), 3);
}
#[test]
fn test_is_positive_negative() {
assert!(Rational::new(1, 2).is_positive());
assert!(!Rational::new(-1, 2).is_positive());
assert!(!Rational::new(0, 1).is_positive());
assert!(Rational::new(-1, 2).is_negative());
assert!(!Rational::new(1, 2).is_negative());
assert!(!Rational::new(0, 1).is_negative());
}
#[test]
fn test_abs() {
assert_eq!(Rational::new(-3, 4).abs(), Rational::new(3, 4));
assert_eq!(Rational::new(3, 4).abs(), Rational::new(3, 4));
assert_eq!(Rational::new(0, 4).abs(), Rational::new(0, 4));
}
#[test]
fn test_reduce() {
let r = Rational::new(4, 8);
let reduced = r.reduce();
assert_eq!(reduced.num(), 1);
assert_eq!(reduced.den(), 2);
let r = Rational::new(6, 9);
let reduced = r.reduce();
assert_eq!(reduced.num(), 2);
assert_eq!(reduced.den(), 3);
let r = Rational::new(0, 5);
let reduced = r.reduce();
assert_eq!(reduced.num(), 0);
assert_eq!(reduced.den(), 1);
}
#[test]
fn test_add() {
let a = Rational::new(1, 2);
let b = Rational::new(1, 4);
let result = a + b;
assert!((result.as_f64() - 0.75).abs() < 0.0001);
}
#[test]
fn test_sub() {
let a = Rational::new(1, 2);
let b = Rational::new(1, 4);
let result = a - b;
assert!((result.as_f64() - 0.25).abs() < 0.0001);
}
#[test]
fn test_mul() {
let a = Rational::new(1, 2);
let b = Rational::new(2, 3);
let result = a * b;
assert!((result.as_f64() - (1.0 / 3.0)).abs() < 0.0001);
}
#[test]
fn test_div() {
let a = Rational::new(1, 2);
let b = Rational::new(2, 3);
let result = a / b;
assert!((result.as_f64() - 0.75).abs() < 0.0001);
}
#[test]
fn test_mul_i32() {
let r = Rational::new(1, 4);
let result = r * 2;
assert!((result.as_f64() - 0.5).abs() < 0.0001);
}
#[test]
fn test_div_i32() {
let r = Rational::new(1, 2);
let result = r / 2;
assert!((result.as_f64() - 0.25).abs() < 0.0001);
}
#[test]
fn test_neg() {
let r = Rational::new(1, 2);
let neg = -r;
assert_eq!(neg.num(), -1);
assert_eq!(neg.den(), 2);
}
#[test]
fn test_ord() {
let a = Rational::new(1, 2);
let b = Rational::new(1, 3);
let c = Rational::new(2, 4);
assert!(a > b);
assert!(b < a);
assert_eq!(a, c);
assert!(a >= c);
assert!(a <= c);
}
#[test]
fn test_from_i32() {
let r: Rational = 5.into();
assert_eq!(r.num(), 5);
assert_eq!(r.den(), 1);
}
#[test]
fn test_from_tuple() {
let r: Rational = (3, 4).into();
assert_eq!(r.num(), 3);
assert_eq!(r.den(), 4);
}
#[test]
fn test_display() {
assert_eq!(format!("{}", Rational::new(1, 2)), "1/2");
assert_eq!(format!("{}", Rational::new(-3, 4)), "-3/4");
}
#[test]
fn test_default() {
assert_eq!(Rational::default(), Rational::one());
}
#[test]
fn test_common_frame_rates() {
let fps = Rational::new(24000, 1001);
assert!((fps.as_f64() - 23.976).abs() < 0.001);
let fps = Rational::new(30000, 1001);
assert!((fps.as_f64() - 29.97).abs() < 0.01);
let fps = Rational::new(60000, 1001);
assert!((fps.as_f64() - 59.94).abs() < 0.01);
}
}
#[test]
fn test_gcd() {
assert_eq!(gcd(12, 8), 4);
assert_eq!(gcd(17, 13), 1);
assert_eq!(gcd(100, 25), 25);
assert_eq!(gcd(0, 5), 5);
assert_eq!(gcd(5, 0), 5);
}
}