Pārlūkot izejas kodu

:sparkles: abs, sqrt, sin, sinh, cos, cosh

Felix Bytow 2 gadi atpakaļ
vecāks
revīzija
00d2ee862a
7 mainītis faili ar 248 papildinājumiem un 136 dzēšanām
  1. 4 0
      CHANGELOG.md
  2. 1 1
      Cargo.toml
  3. 13 1
      src/lib.rs
  4. 69 3
      src/traits/float.rs
  5. 63 119
      src/traits/numeric.rs
  6. 2 2
      src/traits/primitive.rs
  7. 96 10
      src/types/complex.rs

+ 4 - 0
CHANGELOG.md

@@ -1,5 +1,9 @@
 # Changelog
 
+## Release 0.2.2
+
+ - abs, sqrt, sin, cos, sinh and cosh for primitive and complex floats
+
 ## Release 0.2.1
 
  - Forgot to actually make `Complex` visible to the user.

+ 1 - 1
Cargo.toml

@@ -1,7 +1,7 @@
 [package]
 name = "lineal"
 authors = ["Felix Bytow <drako@drako.guru>"]
-version = "0.2.1"
+version = "0.2.2"
 edition = "2021"
 publish = ["crates-drako-guru"]
 

+ 13 - 1
src/lib.rs

@@ -1,4 +1,16 @@
-//! The crate is a LINEar ALgebra library.
+//! This is a LINEar ALgebra library.
+//!
+//! Most stuff works with the traits `Float`, `Numeric` and `Primitive` in some combination.
+//!
+//! The combination `Float + Numeric` allows `f32`, `f64`, `Complex<f32>` and `Complex<f64>`,
+//! whereas `Float + Numeric + Primitive` would only allow `f32` and `f64`.
+//! This trick is used to prevent e.g. `Complex<Complex<f32>>`, as that would not make sense.
+//!
+//! There are no complex numbers with integral types, as these would be rather impractical
+//! when calculating basically anything.
+//!
+//! As a consequence the complex types `Complex<f32>` and `Complex<f64>` are also considered
+//! `Float` in our context.
 pub use self::traits::{Float, Numeric, Primitive};
 pub use self::types::Complex;
 

+ 69 - 3
src/traits/float.rs

@@ -1,11 +1,77 @@
 use std::ops::Neg;
 
 /// Marker trait, because certain operations only make sense on floating point types.
-pub trait Float: Neg<Output=Self> {}
+pub trait Float: Neg<Output=Self> {
+    /// Calculate the absolute value. For primitive floats it just calls their `abs` function.
+    fn fabs(self) -> Self;
 
-impl Float for f32 {}
+    /// Calculate the square root. For primitive floats it just calls their `sqrt` function.
+    fn fsqrt(self) -> Self;
 
-impl Float for f64 {}
+    /// Calculate the sine.
+    fn fsin(self) -> Self;
+
+    /// Calculate the cosine.
+    fn fcos(self) -> Self;
+
+    /// Calculate the hyperbolic sine.
+    fn fsinh(self) -> Self;
+
+    /// Calculate the hyperbolic cosine.
+    fn fcosh(self) -> Self;
+}
+
+impl Float for f32 {
+    fn fabs(self) -> Self {
+        self.abs()
+    }
+
+    fn fsqrt(self) -> Self {
+        self.sqrt()
+    }
+
+    fn fsin(self) -> Self {
+        self.sin()
+    }
+
+    fn fcos(self) -> Self {
+        self.cos()
+    }
+
+    fn fsinh(self) -> Self {
+        self.sinh()
+    }
+
+    fn fcosh(self) -> Self {
+        self.cosh()
+    }
+}
+
+impl Float for f64 {
+    fn fabs(self) -> Self {
+        self.abs()
+    }
+
+    fn fsqrt(self) -> Self {
+        self.sqrt()
+    }
+
+    fn fsin(self) -> Self {
+        self.sin()
+    }
+
+    fn fcos(self) -> Self {
+        self.cos()
+    }
+
+    fn fsinh(self) -> Self {
+        self.sinh()
+    }
+
+    fn fcosh(self) -> Self {
+        self.cosh()
+    }
+}
 
 #[cfg(test)]
 mod tests {

+ 63 - 119
src/traits/numeric.rs

@@ -1,156 +1,97 @@
 use std::fmt::Debug;
 use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
 
-/// Trait representing all numeric primitives.
+/// Trait representing all numeric primitives and `Complex`.
 pub trait Numeric:
 Sized + Copy + Clone + Debug +
 Add<Output=Self> + AddAssign + Sub<Output=Self> + SubAssign +
 Mul<Output=Self> + MulAssign + Div<Output=Self> + DivAssign +
 PartialEq {
-    /// Function returning the 0 value for the given type.
-    fn zero() -> Self;
-
-    /// Function returning the 1 value for the given type.
-    fn one() -> Self;
+    /// Function returning the whole number in the requested type.
+    fn whole(value: u32) -> Self;
 }
 
 impl Numeric for f32 {
-    fn zero() -> Self {
-        0.0
-    }
-
-    fn one() -> Self {
-        1.0
+    fn whole(value: u32) -> Self {
+        value as Self
     }
 }
 
 impl Numeric for f64 {
-    fn zero() -> Self {
-        0.0
-    }
-
-    fn one() -> Self {
-        1.0
+    fn whole(value: u32) -> Self {
+        value as Self
     }
 }
 
 impl Numeric for i8 {
-    fn zero() -> Self {
-        0
-    }
-
-    fn one() -> Self {
-        1
+    fn whole(value: u32) -> Self {
+        value as Self
     }
 }
 
 impl Numeric for i16 {
-    fn zero() -> Self {
-        0
-    }
-
-    fn one() -> Self {
-        1
+    fn whole(value: u32) -> Self {
+        value as Self
     }
 }
 
 impl Numeric for i32 {
-    fn zero() -> Self {
-        0
-    }
-
-    fn one() -> Self {
-        1
+    fn whole(value: u32) -> Self {
+        value as Self
     }
 }
 
 impl Numeric for i64 {
-    fn zero() -> Self {
-        0
-    }
-
-    fn one() -> Self {
-        1
+    fn whole(value: u32) -> Self {
+        value as Self
     }
 }
 
 impl Numeric for i128 {
-    fn zero() -> Self {
-        0
-    }
-
-    fn one() -> Self {
-        1
+    fn whole(value: u32) -> Self {
+        value as Self
     }
 }
 
 impl Numeric for isize {
-    fn zero() -> Self {
-        0
-    }
-
-    fn one() -> Self {
-        1
+    fn whole(value: u32) -> Self {
+        value as Self
     }
 }
 
 impl Numeric for u8 {
-    fn zero() -> Self {
-        0
-    }
-
-    fn one() -> Self {
-        1
+    fn whole(value: u32) -> Self {
+        value as Self
     }
 }
 
 impl Numeric for u16 {
-    fn zero() -> Self {
-        0
-    }
-
-    fn one() -> Self {
-        1
+    fn whole(value: u32) -> Self {
+        value as Self
     }
 }
 
 impl Numeric for u32 {
-    fn zero() -> Self {
-        0
-    }
-
-    fn one() -> Self {
-        1
+    fn whole(value: u32) -> Self {
+        value as Self
     }
 }
 
 impl Numeric for u64 {
-    fn zero() -> Self {
-        0
-    }
-
-    fn one() -> Self {
-        1
+    fn whole(value: u32) -> Self {
+        value as Self
     }
 }
 
 impl Numeric for u128 {
-    fn zero() -> Self {
-        0
-    }
-
-    fn one() -> Self {
-        1
+    fn whole(value: u32) -> Self {
+        value as Self
     }
 }
 
 impl Numeric for usize {
-    fn zero() -> Self {
-        0
-    }
-
-    fn one() -> Self {
-        1
+    fn whole(value: u32) -> Self {
+        value as Self
     }
 }
 
@@ -160,38 +101,41 @@ mod tests {
 
     #[test]
     fn floats_are_numeric() {
-        assert_eq!(f32::zero(), 0.0);
-        assert_eq!(f32::one(), 1.0);
-        assert_eq!(f64::zero(), 0.0);
-        assert_eq!(f64::one(), 1.0);
+        assert_eq!(f32::whole(0), 0.0);
+        assert_eq!(f32::whole(1), 1.0);
+        assert_eq!(f64::whole(0), 0.0);
+        assert_eq!(f64::whole(1), 1.0);
     }
 
     #[test]
     fn integers_are_numeric() {
-        assert_eq!(i8::zero(), 0);
-        assert_eq!(i8::one(), 1);
-        assert_eq!(i16::zero(), 0);
-        assert_eq!(i16::one(), 1);
-        assert_eq!(i32::zero(), 0);
-        assert_eq!(i32::one(), 1);
-        assert_eq!(i64::zero(), 0);
-        assert_eq!(i64::one(), 1);
-        assert_eq!(i128::zero(), 0);
-        assert_eq!(i128::one(), 1);
-        assert_eq!(isize::zero(), 0);
-        assert_eq!(isize::one(), 1);
-
-        assert_eq!(u8::zero(), 0);
-        assert_eq!(u8::one(), 1);
-        assert_eq!(u16::zero(), 0);
-        assert_eq!(u16::one(), 1);
-        assert_eq!(u32::zero(), 0);
-        assert_eq!(u32::one(), 1);
-        assert_eq!(u64::zero(), 0);
-        assert_eq!(u64::one(), 1);
-        assert_eq!(u128::zero(), 0);
-        assert_eq!(u128::one(), 1);
-        assert_eq!(usize::zero(), 0);
-        assert_eq!(usize::one(), 1);
+        // not sure yet, if I want this to be possible
+        assert_eq!(i8::whole(255), -1);
+
+        assert_eq!(i8::whole(0), 0);
+        assert_eq!(i8::whole(1), 1);
+        assert_eq!(i16::whole(0), 0);
+        assert_eq!(i16::whole(1), 1);
+        assert_eq!(i32::whole(0), 0);
+        assert_eq!(i32::whole(1), 1);
+        assert_eq!(i64::whole(0), 0);
+        assert_eq!(i64::whole(1), 1);
+        assert_eq!(i128::whole(0), 0);
+        assert_eq!(i128::whole(1), 1);
+        assert_eq!(isize::whole(0), 0);
+        assert_eq!(isize::whole(1), 1);
+
+        assert_eq!(u8::whole(0), 0);
+        assert_eq!(u8::whole(1), 1);
+        assert_eq!(u16::whole(0), 0);
+        assert_eq!(u16::whole(1), 1);
+        assert_eq!(u32::whole(0), 0);
+        assert_eq!(u32::whole(1), 1);
+        assert_eq!(u64::whole(0), 0);
+        assert_eq!(u64::whole(1), 1);
+        assert_eq!(u128::whole(0), 0);
+        assert_eq!(u128::whole(1), 1);
+        assert_eq!(usize::whole(0), 0);
+        assert_eq!(usize::whole(1), 1);
     }
 }

+ 2 - 2
src/traits/primitive.rs

@@ -1,5 +1,5 @@
-/// Marker trait for primitive types.
-pub trait Primitive {}
+/// Marker trait for primitive types, because some stuff doesn't work with complex numbers.
+pub trait Primitive: PartialOrd {}
 
 impl Primitive for f32 {}
 

+ 96 - 10
src/types/complex.rs

@@ -3,10 +3,38 @@ use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssi
 use crate::{Float, Numeric, Primitive};
 
 /// Struct representing a complex number.
+///
+/// # Example
+///
+/// ```rust
+/// # use lineal::{Complex, Float, Numeric};
+/// let c = Complex { real: 9.0, imag: 0.0 };
+/// assert_eq!(format!("{:?}", c), "[9,0]");
+///
+/// let three = c.fsqrt().real;
+/// assert_eq!(three, 3.0);
+///
+/// let i: Complex<f32> = (-Complex::whole(1)).fsqrt();
+/// assert_eq!(i, Complex::i());
+///
+/// let a = Complex { real: 2.0, imag: 0.5 };
+/// let a_squared = a * a;
+/// assert_eq!(a_squared, Complex { real: 3.75, imag: 2.0 });
+/// assert_eq!(a_squared.fsqrt(), a);
+/// ```
 #[derive(Debug, Copy, Clone)]
 pub struct Complex<T: Float + Numeric + Primitive> {
-    real: T,
-    imag: T,
+    pub real: T,
+    pub imag: T,
+}
+
+impl<T: Float + Numeric + Primitive> Complex<T> {
+    pub fn i() -> Self {
+        Self {
+            real: T::whole(0),
+            imag: T::whole(1),
+        }
+    }
 }
 
 impl<T: Float + Numeric + Primitive> Add for Complex<T> {
@@ -49,7 +77,7 @@ impl<T: Float + Numeric + Primitive> Mul for Complex<T> {
     type Output = Self;
 
     fn mul(self, rhs: Self) -> Self::Output {
-        Complex {
+        Self {
             real: self.real * rhs.real - self.imag * rhs.imag,
             imag: self.real * rhs.imag + self.imag * rhs.real,
         }
@@ -101,21 +129,70 @@ impl<T: Float + Numeric + Primitive> PartialEq for Complex<T> {
     }
 }
 
-impl<T: Float + Numeric + Primitive> Float for Complex<T> {}
+impl<T: Float + Primitive + Numeric> Float for Complex<T> {
+    fn fabs(self) -> Self {
+        Self {
+            real: (self.real * self.real + self.imag * self.imag).fsqrt(),
+            imag: T::whole(0),
+        }
+    }
+
+    fn fsqrt(self) -> Self {
+        let zero = T::whole(0);
+        let two = T::whole(2);
+        let abs_z = self.fabs().real;
 
-impl<T: Float + Numeric + Primitive> Numeric for Complex<T> {
-    fn zero() -> Self {
-        Self { real: T::zero(), imag: T::zero() }
+        let base_re = if self.imag == zero { self.real } else { (abs_z + self.real) / two };
+        let re = if base_re < zero { zero } else { base_re.fsqrt() };
+
+        let im = if self.imag == zero { zero } else {
+            (self.imag / self.imag.fabs()) * ((abs_z - self.real) / two).fsqrt()
+        } + if base_re < zero { (-base_re).fsqrt() } else { zero };
+
+        Self {
+            real: re,
+            imag: im,
+        }
+    }
+
+    fn fsin(self) -> Self {
+        Self {
+            real: self.real.fsin() * self.imag.fcosh(),
+            imag: self.real.fcos() * self.imag.fsinh(),
+        }
+    }
+
+    fn fcos(self) -> Self {
+        Self {
+            real: self.real.fcos() * self.imag.fcosh(),
+            imag: self.real.fsin() * self.imag.fsinh(),
+        }
+    }
+
+    fn fsinh(self) -> Self {
+        Self {
+            real: self.real.fsinh() * self.imag.fcos(),
+            imag: self.real.fcosh() * self.imag.fsin(),
+        }
     }
 
-    fn one() -> Self {
-        Self { real: T::one(), imag: T::zero() }
+    fn fcosh(self) -> Self {
+        Self {
+            real: self.real.fcosh() * self.imag.fcos(),
+            imag: self.real.fsinh() * self.imag.fsin(),
+        }
+    }
+}
+
+impl<T: Float + Numeric + Primitive> Numeric for Complex<T> {
+    fn whole(value: u32) -> Self {
+        Self { real: T::whole(value), imag: T::whole(0) }
     }
 }
 
 #[cfg(test)]
 mod tests {
-    use crate::Complex;
+    use crate::{Complex, Float};
 
     #[test]
     fn division() {
@@ -125,4 +202,13 @@ mod tests {
         a /= b;
         assert_eq!(format!("{:?}", a), format!("{:?}", expected));
     }
+
+    #[test]
+    fn square_root_with_negatives() {
+        let a = Complex { real: -1.0, imag: -1.0 };
+        let root = a.fsqrt();
+        let expected = Complex { real: 0.45508986, imag: -1.09868411 };
+        assert!((root.real - expected.real).fabs() < 0.00001);
+        assert!((root.imag - expected.imag).fabs() < 0.00001);
+    }
 }