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 | |
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')
-rw-r--r-- | crates/core/src/numerics/mod.rs | 102 | ||||
-rw-r--r-- | crates/core/src/numerics/rational.rs | 147 |
2 files changed, 249 insertions, 0 deletions
diff --git a/crates/core/src/numerics/mod.rs b/crates/core/src/numerics/mod.rs new file mode 100644 index 0000000..9e3d782 --- /dev/null +++ b/crates/core/src/numerics/mod.rs @@ -0,0 +1,102 @@ +//! Number traits and utilities. + +use core::{ + mem, + ops::{ + Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Sub, + SubAssign, + }, +}; + +pub mod rational; + +//NOTE: these should probably be broken up a bunch; they're only implemented +// this way because i didn't really need much else to implement +// rationals. ideally we should break them up on the lines of rings, +// groups, fields, unique factorization domains, principal ideal domains, +// ordered rings, etc. this would make further extensions to +// functionality in azimuth easier. + +/// Integer. +pub trait Integer: + Eq + + Ord + + Mul<Output = Self> + + Div<Output = Self> + + Add<Output = Self> + + Sub<Output = Self> + + Rem<Output = Self> + + MulAssign + + DivAssign + + AddAssign + + SubAssign + + RemAssign + + Sized + + Copy + + Clone +{ + //NOTE: `Neg` is neglected here as the primary purpose of this trait is + // to abstract over integers for the `Rational` type. as stated + // above, i'd like to adjust the way all of this works anyway for + // future extensions, but for now, we don't really need it. + + //NOTE: `Copy` is required for now as bignum support is not a requirement + // yet and this makes implementation simpler. + + /// Additive identity. + const ZERO: Self; + + /// Multiplicative identity. + const ONE: Self; + + /// Maximum attainable value. + const MAX: Self; + + /// Minimum attainable value. + const MIN: Self; + + /// Calculates the greatest common divisor of this number and another + fn gcd(&self, rhs: &Self) -> Self; +} + +/// Helper macro for the implementation of `Integer` +macro_rules! integer_impl { + ($type:ty, $zero:expr, $one:expr, $max:expr, $min:expr) => { + impl Integer for $type { + const ZERO: Self = $zero; + const ONE: Self = $one; + const MAX: Self = $max; + const MIN: Self = $min; + + #[inline] + fn gcd(&self, rhs: &Self) -> Self { + let mut m = *self; + let mut n = *rhs; + + if m < n { + mem::swap(&mut m, &mut n); + } + + while n != 0 { + let t = n; + n = m % n; + m = t; + } + + m + } + } + }; +} + +integer_impl!(i8, 0, 1, i8::MAX, i8::MIN); +integer_impl!(i16, 0, 1, i16::MAX, i16::MIN); +integer_impl!(i32, 0, 1, i32::MAX, i32::MIN); +integer_impl!(i64, 0, 1, i64::MAX, i64::MIN); +integer_impl!(i128, 0, 1, i128::MAX, i128::MIN); + +integer_impl!(u8, 0, 1, u8::MAX, u8::MIN); +integer_impl!(u16, 0, 1, u16::MAX, u16::MIN); +integer_impl!(u32, 0, 1, u32::MAX, u32::MIN); +integer_impl!(u64, 0, 1, u64::MAX, u64::MIN); +integer_impl!(u128, 0, 1, u128::MAX, u128::MIN); 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) + ); + } +} |