Skip to main content

neco_complex/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2#![forbid(unsafe_code)]
3#![doc = include_str!("../README.md")]
4
5use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
6
7#[repr(C)]
8#[derive(Clone, Copy, Debug, PartialEq)]
9pub struct Complex<T> {
10    re: T,
11    im: T,
12}
13
14impl<T> Complex<T> {
15    pub const fn new(re: T, im: T) -> Self {
16        Self { re, im }
17    }
18
19    pub fn real(&self) -> &T {
20        &self.re
21    }
22
23    pub fn imaginary(&self) -> &T {
24        &self.im
25    }
26
27    pub fn into_parts(self) -> (T, T) {
28        (self.re, self.im)
29    }
30
31    pub fn real_value(self) -> T
32    where
33        T: Copy,
34    {
35        self.re
36    }
37
38    pub fn imaginary_value(self) -> T
39    where
40        T: Copy,
41    {
42        self.im
43    }
44
45    pub fn set_real(&mut self, value: T) {
46        self.re = value;
47    }
48
49    pub fn set_imaginary(&mut self, value: T) {
50        self.im = value;
51    }
52}
53
54impl Complex<f32> {
55    pub const fn zero() -> Self {
56        Self::new(0.0, 0.0)
57    }
58
59    pub const fn one() -> Self {
60        Self::new(1.0, 0.0)
61    }
62}
63
64impl Complex<f64> {
65    pub const fn zero() -> Self {
66        Self::new(0.0, 0.0)
67    }
68
69    pub const fn one() -> Self {
70        Self::new(1.0, 0.0)
71    }
72
73    pub const fn from_real(value: f64) -> Self {
74        Self::new(value, 0.0)
75    }
76
77    #[cfg(feature = "std")]
78    pub fn argument(self) -> f64 {
79        self.im.atan2(self.re)
80    }
81}
82
83#[cfg(feature = "std")]
84impl Complex<f32> {
85    pub fn argument(self) -> f32 {
86        self.im.atan2(self.re)
87    }
88}
89
90impl<T> Complex<T>
91where
92    T: Copy + Neg<Output = T>,
93{
94    pub fn conjugate(self) -> Self {
95        Self::new(self.re, -self.im)
96    }
97}
98
99impl<T> Complex<T>
100where
101    T: Copy + Add<Output = T> + Mul<Output = T>,
102{
103    pub fn norm_squared(self) -> T {
104        self.re * self.re + self.im * self.im
105    }
106}
107
108#[cfg(feature = "std")]
109impl Complex<f64> {
110    pub fn norm(self) -> f64 {
111        self.re.hypot(self.im)
112    }
113}
114
115impl<T> Neg for Complex<T>
116where
117    T: Neg<Output = T>,
118{
119    type Output = Self;
120
121    fn neg(self) -> Self {
122        Self::new(-self.re, -self.im)
123    }
124}
125
126impl<T> Add for Complex<T>
127where
128    T: Add<Output = T>,
129{
130    type Output = Self;
131
132    fn add(self, rhs: Self) -> Self {
133        Self::new(self.re + rhs.re, self.im + rhs.im)
134    }
135}
136
137impl<T> Sub for Complex<T>
138where
139    T: Sub<Output = T>,
140{
141    type Output = Self;
142
143    fn sub(self, rhs: Self) -> Self {
144        Self::new(self.re - rhs.re, self.im - rhs.im)
145    }
146}
147
148impl<T> Mul for Complex<T>
149where
150    T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Output = T>,
151{
152    type Output = Self;
153
154    fn mul(self, rhs: Self) -> Self {
155        Self::new(
156            self.re * rhs.re - self.im * rhs.im,
157            self.re * rhs.im + self.im * rhs.re,
158        )
159    }
160}
161
162impl<T> Div for Complex<T>
163where
164    T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T>,
165{
166    type Output = Self;
167
168    fn div(self, rhs: Self) -> Self {
169        let denominator = rhs.re * rhs.re + rhs.im * rhs.im;
170        Self::new(
171            (self.re * rhs.re + self.im * rhs.im) / denominator,
172            (self.im * rhs.re - self.re * rhs.im) / denominator,
173        )
174    }
175}
176
177impl<T> Mul<T> for Complex<T>
178where
179    T: Copy + Mul<Output = T>,
180{
181    type Output = Self;
182
183    fn mul(self, rhs: T) -> Self {
184        Self::new(self.re * rhs, self.im * rhs)
185    }
186}
187
188impl<T> Div<T> for Complex<T>
189where
190    T: Copy + Div<Output = T>,
191{
192    type Output = Self;
193
194    fn div(self, rhs: T) -> Self {
195        Self::new(self.re / rhs, self.im / rhs)
196    }
197}
198
199impl<T> AddAssign for Complex<T>
200where
201    T: AddAssign,
202{
203    fn add_assign(&mut self, rhs: Self) {
204        self.re += rhs.re;
205        self.im += rhs.im;
206    }
207}
208
209impl<T> SubAssign for Complex<T>
210where
211    T: SubAssign,
212{
213    fn sub_assign(&mut self, rhs: Self) {
214        self.re -= rhs.re;
215        self.im -= rhs.im;
216    }
217}
218
219impl<T> MulAssign for Complex<T>
220where
221    T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Output = T>,
222{
223    fn mul_assign(&mut self, rhs: Self) {
224        *self = *self * rhs;
225    }
226}
227
228impl<T> DivAssign for Complex<T>
229where
230    T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T>,
231{
232    fn div_assign(&mut self, rhs: Self) {
233        *self = *self / rhs;
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::Complex;
240
241    #[test]
242    fn complex_arithmetic_preserves_components() {
243        let left = Complex::new(1.0_f64, 2.0);
244        let right = Complex::new(3.0_f64, -1.0);
245        assert_eq!(left + right, Complex::new(4.0, 1.0));
246        assert_eq!(left - right, Complex::new(-2.0, 3.0));
247        assert_eq!(left * right, Complex::new(5.0, 5.0));
248        assert_eq!(left.conjugate(), Complex::new(1.0, -2.0));
249    }
250
251    #[test]
252    fn division_recovers_the_input_value() {
253        let value = Complex::new(1.0_f64, 2.0);
254        let factor = Complex::new(3.0_f64, -1.0);
255        assert_eq!((value * factor) / factor, value);
256    }
257
258    #[test]
259    fn norm_squared_is_available_in_each_runtime_configuration() {
260        assert_eq!(Complex::new(3.0_f64, 4.0).norm_squared(), 25.0);
261    }
262
263    #[test]
264    fn components_are_observed_and_replaced_through_methods() {
265        let mut value = Complex::new(1.0_f64, -2.0);
266        assert_eq!(value.real_value(), 1.0);
267        assert_eq!(value.imaginary_value(), -2.0);
268        value.set_real(3.0);
269        value.set_imaginary(4.0);
270        assert_eq!(value.into_parts(), (3.0, 4.0));
271    }
272
273    #[cfg(feature = "std")]
274    #[test]
275    fn norm_is_the_euclidean_magnitude() {
276        assert_eq!(Complex::new(3.0_f64, 4.0).norm(), 5.0);
277    }
278
279    #[cfg(feature = "std")]
280    #[test]
281    fn argument_uses_the_component_quadrants() {
282        assert_eq!(
283            Complex::new(0.0_f64, 1.0).argument(),
284            core::f64::consts::FRAC_PI_2
285        );
286    }
287}