about summary refs log tree commit diff stats
path: root/crates/core/src/numerics/rational.rs
diff options
context:
space:
mode:
authorsuperwhiskers <[email protected]>2025-08-27 14:41:19 -0500
committersuperwhiskers <[email protected]>2025-09-15 10:55:10 -0500
commit83751efd734999fc11316a66317250ca53e76726 (patch)
treef5917c5c0bc8fd5883f7893eb5d4b9853585aea7 /crates/core/src/numerics/rational.rs
parent386279ce28a54002fa91f436d5b60815c537e910 (diff)
downloadazimuth-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.rs147
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)
+        );
+    }
+}