Skip to main content

oxinum_complex/native/
complex.rs

1//! Native `BigComplex` — a binary-base arbitrary-precision complex number
2//! built as the ordered pair `(re, im)` of [`BigFloat`]s.
3//!
4//! The value of a `BigComplex` is `re + im·i`, where both components are
5//! native binary [`BigFloat`]s. Each component carries its own precision
6//! and (via [`BigFloat`]) full IEEE-754-style non-finite support; arithmetic
7//! and transcendental routines added by sibling modules reconcile precision
8//! and rounding as required.
9//!
10//! # Why `Hash`, `Eq`, and `Ord` are not implemented
11//!
12//! The complex field has no order compatible with its ring structure, so no
13//! `Ord` / `PartialOrd` is provided. `BigFloat` itself implements neither
14//! `Eq` (NaN breaks reflexivity) nor `Hash`, so `BigComplex` inherits the
15//! same constraints; component-wise `PartialEq` is available where useful.
16
17use oxinum_core::OxiNumResult;
18use oxinum_float::native::{BigFloat, RoundingMode};
19
20/// Native arbitrary-precision binary complex number `re + im·i`.
21///
22/// Both components are [`BigFloat`] values. See the module-level
23/// documentation for the rationale behind the absence of `Hash`, `Eq`, and
24/// `Ord`.
25///
26/// # Examples
27///
28/// ```
29/// use oxinum_complex::native::{BigComplex, RoundingMode};
30///
31/// let z = BigComplex::from_f64(3.0, 4.0, 53).expect("finite parts");
32/// // |3 + 4i|^2 = 25.
33/// assert_eq!(z.norm_sqr().to_f64(), 25.0);
34/// ```
35#[derive(Clone)]
36pub struct BigComplex {
37    pub(crate) re: BigFloat,
38    pub(crate) im: BigFloat,
39}
40
41impl BigComplex {
42    /// Construct a complex number from its real and imaginary parts.
43    pub fn from_parts(re: BigFloat, im: BigFloat) -> Self {
44        Self { re, im }
45    }
46
47    /// Construct a complex number from its real and imaginary parts.
48    ///
49    /// Alias of [`BigComplex::from_parts`].
50    pub fn new(re: BigFloat, im: BigFloat) -> Self {
51        Self::from_parts(re, im)
52    }
53
54    /// Construct a purely real complex number (`im = 0` at `re`'s precision).
55    pub fn from_real(re: BigFloat) -> Self {
56        let prec = re.precision();
57        Self {
58            re,
59            im: BigFloat::zero(prec),
60        }
61    }
62
63    /// Construct a purely imaginary complex number (`re = 0` at `im`'s precision).
64    pub fn from_imag(im: BigFloat) -> Self {
65        let prec = im.precision();
66        Self {
67            re: BigFloat::zero(prec),
68            im,
69        }
70    }
71
72    /// The additive identity `0 + 0·i` at `prec` bits of precision.
73    pub fn zero(prec: u32) -> Self {
74        Self {
75            re: BigFloat::zero(prec),
76            im: BigFloat::zero(prec),
77        }
78    }
79
80    /// The multiplicative identity `1 + 0·i` at `prec` bits of precision.
81    pub fn one(prec: u32, mode: RoundingMode) -> Self {
82        Self {
83            re: BigFloat::from_i64(1, prec, mode),
84            im: BigFloat::zero(prec),
85        }
86    }
87
88    /// The imaginary unit `0 + 1·i` at `prec` bits of precision.
89    pub fn i(prec: u32, mode: RoundingMode) -> Self {
90        Self {
91            re: BigFloat::zero(prec),
92            im: BigFloat::from_i64(1, prec, mode),
93        }
94    }
95
96    /// Construct a complex number from a pair of `f64` values at `prec` bits.
97    ///
98    /// Delegates to [`BigFloat::from_f64`] for each component.
99    ///
100    /// # Errors
101    ///
102    /// Propagates the [`OxiNumError`](oxinum_core::OxiNumError) returned by
103    /// [`BigFloat::from_f64`] when a component is `NaN` or infinite — the
104    /// native `BigFloat` rejects those inputs.
105    ///
106    /// # Examples
107    ///
108    /// ```
109    /// use oxinum_complex::native::BigComplex;
110    /// let z = BigComplex::from_f64(1.5, -2.25, 53).expect("finite parts");
111    /// assert_eq!(z.re().to_f64(), 1.5);
112    /// assert_eq!(z.im().to_f64(), -2.25);
113    /// ```
114    pub fn from_f64(re: f64, im: f64, prec: u32) -> OxiNumResult<Self> {
115        Ok(Self {
116            re: BigFloat::from_f64(re, prec)?,
117            im: BigFloat::from_f64(im, prec)?,
118        })
119    }
120
121    /// A shared reference to the real part.
122    pub fn re(&self) -> &BigFloat {
123        &self.re
124    }
125
126    /// A shared reference to the imaginary part.
127    pub fn im(&self) -> &BigFloat {
128        &self.im
129    }
130
131    /// A clone of the real part.
132    pub fn real(&self) -> BigFloat {
133        self.re.clone()
134    }
135
136    /// A clone of the imaginary part.
137    pub fn imag(&self) -> BigFloat {
138        self.im.clone()
139    }
140
141    /// Decompose into the owned `(re, im)` pair.
142    pub fn into_parts(self) -> (BigFloat, BigFloat) {
143        (self.re, self.im)
144    }
145
146    /// The complex conjugate `re − im·i`.
147    ///
148    /// # Examples
149    ///
150    /// ```
151    /// use oxinum_complex::native::BigComplex;
152    /// let z = BigComplex::from_f64(2.0, 3.0, 53).expect("finite parts");
153    /// let c = z.conj();
154    /// assert_eq!(c.re().to_f64(), 2.0);
155    /// assert_eq!(c.im().to_f64(), -3.0);
156    /// ```
157    pub fn conj(&self) -> Self {
158        Self {
159            re: self.re.clone(),
160            im: -&self.im,
161        }
162    }
163
164    /// The squared magnitude `re² + im²` (always real, non-negative).
165    ///
166    /// # Examples
167    ///
168    /// ```
169    /// use oxinum_complex::native::BigComplex;
170    /// let z = BigComplex::from_f64(3.0, 4.0, 53).expect("finite parts");
171    /// assert_eq!(z.norm_sqr().to_f64(), 25.0);
172    /// ```
173    pub fn norm_sqr(&self) -> BigFloat {
174        &(&self.re * &self.re) + &(&self.im * &self.im)
175    }
176
177    /// Returns `true` if both components are the canonical zero.
178    pub fn is_zero(&self) -> bool {
179        self.re.is_zero() && self.im.is_zero()
180    }
181}
182
183// ---------------------------------------------------------------------------
184// Tests
185// ---------------------------------------------------------------------------
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190
191    #[test]
192    fn zero_one_i_constructors() {
193        let z = BigComplex::zero(53);
194        assert!(z.is_zero());
195
196        let one = BigComplex::one(53, RoundingMode::HalfEven);
197        assert_eq!(one.re().to_f64(), 1.0);
198        assert_eq!(one.im().to_f64(), 0.0);
199
200        let imag = BigComplex::i(53, RoundingMode::HalfEven);
201        assert_eq!(imag.re().to_f64(), 0.0);
202        assert_eq!(imag.im().to_f64(), 1.0);
203    }
204
205    #[test]
206    fn from_parts_round_trip() {
207        let z = BigComplex::from_f64(2.5, -7.0, 53).expect("finite parts");
208        let (re, im) = z.clone().into_parts();
209        assert_eq!(re.to_f64(), 2.5);
210        assert_eq!(im.to_f64(), -7.0);
211        assert_eq!(z.real().to_f64(), 2.5);
212        assert_eq!(z.imag().to_f64(), -7.0);
213    }
214
215    #[test]
216    fn conj_and_norm_sqr() {
217        let z = BigComplex::from_f64(3.0, 4.0, 53).expect("finite parts");
218        let c = z.conj();
219        assert_eq!(c.re().to_f64(), 3.0);
220        assert_eq!(c.im().to_f64(), -4.0);
221        assert_eq!(z.norm_sqr().to_f64(), 25.0);
222    }
223}