diff options
author | superwhiskers <[email protected]> | 2025-08-27 14:41:19 -0500 |
---|---|---|
committer | superwhiskers <[email protected]> | 2025-09-15 10:55:10 -0500 |
commit | 83751efd734999fc11316a66317250ca53e76726 (patch) | |
tree | f5917c5c0bc8fd5883f7893eb5d4b9853585aea7 /crates/core/src/numerics/rational.rs | |
parent | 386279ce28a54002fa91f436d5b60815c537e910 (diff) | |
download | azimuth-83751efd734999fc11316a66317250ca53e76726.tar.gz azimuth-83751efd734999fc11316a66317250ca53e76726.tar.bz2 azimuth-83751efd734999fc11316a66317250ca53e76726.zip |
initial expression implementation
Change-Id: I6a6a69640c133bce112891bba09033b08e7c0dec
Diffstat (limited to 'crates/core/src/numerics/rational.rs')
-rw-r--r-- | crates/core/src/numerics/rational.rs | 147 |
1 files changed, 147 insertions, 0 deletions
diff --git a/crates/core/src/numerics/rational.rs b/crates/core/src/numerics/rational.rs new file mode 100644 index 0000000..e814dbd --- /dev/null +++ b/crates/core/src/numerics/rational.rs @@ -0,0 +1,147 @@ +//! 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<T> { + numerator: T, + denominator: T, +} + +impl<T> Rational<T> +where + T: Integer, +{ + /// Constructs a new `Rational<T>`. + /// + /// # 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<T>`. + /// + /// # Errors + /// + /// Returns an error if the denominator is zero. + pub fn try_new(numerator: T, denominator: T) -> Result<Self, Error> { + 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; + } + } +} + +/// 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, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + fmt.write_str("rational number construction failed")?; + let reason = match self { + Error::ZeroDenominator => " because the denominator was zero", + }; + fmt.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) + ); + } +} |