//! Rational numbers. //TODO: implement overloads for `Rational`. //TODO: reduce rational numbers before construction is finished. use crate::numerics::Integer; #[cfg(feature = "core-fmt")] use core::fmt; #[cfg(feature = "core-error")] use core::error; /// Rational number. /// /// All operations #[derive(Eq, PartialEq, Copy, Clone)] #[cfg_attr(feature = "core-fmt", derive(Debug))] pub struct Rational { numerator: T, denominator: T, } impl Rational where T: Integer, { /// Constructs a new `Rational`. /// /// # Panics /// /// Panics if the denominator is zero. #[cfg(feature = "core-fmt")] pub fn new(numerator: T, denominator: T) -> Self { //PANIC: this is acceptable as the panic is mentioned above and it is // used to assert an invariant. #[allow(clippy::expect_used)] Self::try_new(numerator, denominator) .expect("denominator should not be zero") } /// Constructs a new `Rational`. /// /// # Errors /// /// Returns an error if the denominator is zero. pub fn try_new(numerator: T, denominator: T) -> Result { if denominator == T::ZERO { return Err(Error::ZeroDenominator); } let mut rational = Self { numerator, denominator, }; rational.reduce(); Ok(rational) } /// Converts the rational into its components. #[inline] pub fn into_parts(self) -> (T, T) { (self.numerator, self.denominator) } /// Returns both the numerator and denominator of the rational. #[inline] pub fn as_parts(&self) -> (&T, &T) { (&self.numerator, &self.denominator) } /// Returns the numerator of the rational. #[inline] pub fn numerator(&self) -> &T { &self.numerator } /// Returns the denominator of the rational. #[inline] pub fn denominator(&self) -> &T { &self.denominator } /// Reduces the rational so that the numerator and denominator have no /// common factors and the denominator is greater than the zero element. fn reduce(&mut self) { if self.numerator == T::ZERO { self.denominator = T::ONE; return; } if self.numerator == self.denominator { self.numerator = T::ONE; self.denominator = T::ONE; return; } let gcd = self.numerator.gcd(&self.denominator); self.numerator /= gcd; self.denominator /= gcd; if self.denominator < T::ZERO { self.numerator = T::ZERO - self.numerator; self.denominator = T::ZERO - self.denominator; } } } #[cfg(feature = "core-fmt")] impl fmt::Display for Rational where T: Integer + fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}/{}", self.numerator, self.denominator) } } /// Representation of an error that occurred within [`Rational`]. #[non_exhaustive] #[derive(Eq, PartialEq)] #[cfg_attr(feature = "core-fmt", derive(Debug))] pub enum Error { /// Denominator was equal to zero. ZeroDenominator, } #[cfg(feature = "core-fmt")] impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("rational number construction failed")?; let reason = match self { Error::ZeroDenominator => " because the denominator was zero", }; f.write_str(reason) } } #[cfg(feature = "core-error")] impl error::Error for Error {} #[cfg(all(test, feature = "core-error"))] mod test { #![allow(clippy::expect_used)] use super::*; #[test] fn rational_sanity() { assert_eq!( Rational::try_new(6, 3) .expect("unable to construct a rational number") .into_parts(), (2, 1) ); } }