Skip to main content

oxinum_complex/native/
convert.rs

1//! Conversions between native [`BigComplex`] and other numeric types.
2//!
3//! Provides the infallible `From` constructors for the ordered `(re, im)`
4//! [`BigFloat`] pair and for a purely-real [`BigFloat`] (placed on the real
5//! axis, `im = 0` at the real part's precision), plus a lossy projection to a
6//! pair of `f64`s via [`BigFloat::to_f64`].
7
8use core::convert::TryFrom;
9use oxinum_core::{OxiNumError, OxiNumResult};
10use oxinum_float::native::BigFloat;
11
12use super::BigComplex;
13
14impl From<(BigFloat, BigFloat)> for BigComplex {
15    /// Build `re + im·i` from the ordered pair `(re, im)`.
16    #[inline]
17    fn from((re, im): (BigFloat, BigFloat)) -> Self {
18        BigComplex::from_parts(re, im)
19    }
20}
21
22impl From<BigFloat> for BigComplex {
23    /// Place a real [`BigFloat`] on the real axis (`im = 0` at `re`'s precision).
24    #[inline]
25    fn from(re: BigFloat) -> Self {
26        BigComplex::from_real(re)
27    }
28}
29
30impl BigComplex {
31    /// Project to a pair of `f64`s `(re, im)` via [`BigFloat::to_f64`].
32    ///
33    /// This is a lossy convenience conversion: each component is rounded to the
34    /// nearest `f64` (and non-finite components map to the corresponding
35    /// `f64::NAN` / `f64::INFINITY`, matching `BigFloat::to_f64`).
36    ///
37    /// # Examples
38    ///
39    /// ```
40    /// use oxinum_complex::native::BigComplex;
41    /// let z = BigComplex::from_f64(1.5, -2.25, 80).expect("finite");
42    /// let (re, im) = z.to_f64_parts();
43    /// assert_eq!(re, 1.5);
44    /// assert_eq!(im, -2.25);
45    /// ```
46    pub fn to_f64_parts(&self) -> (f64, f64) {
47        (self.re().to_f64(), self.im().to_f64())
48    }
49}
50
51/// Try to project both components to `f64`, returning an error if either
52/// component is non-finite (infinite or NaN) after the conversion.
53///
54/// # Errors
55///
56/// Returns [`OxiNumError::Overflow`] when either the real or imaginary part
57/// exceeds `f64::MAX` in magnitude (i.e. the result would be ±∞ or NaN).
58impl TryFrom<&BigComplex> for (f64, f64) {
59    type Error = OxiNumError;
60
61    fn try_from(z: &BigComplex) -> OxiNumResult<(f64, f64)> {
62        let (re, im) = z.to_f64_parts();
63        if !re.is_finite() || !im.is_finite() {
64            return Err(OxiNumError::Overflow(
65                "component is non-finite (infinite or NaN) after f64 projection".into(),
66            ));
67        }
68        Ok((re, im))
69    }
70}
71
72// ---------------------------------------------------------------------------
73// Optional num-complex interop
74// ---------------------------------------------------------------------------
75
76#[cfg(feature = "num-complex")]
77mod num_complex_impls {
78    use num_complex::Complex;
79    use oxinum_float::native::{BigFloat, RoundingMode};
80
81    use super::BigComplex;
82
83    const DEFAULT_PREC: u32 = 80;
84    const DEFAULT_MODE: RoundingMode = RoundingMode::HalfEven;
85
86    /// Build a [`BigComplex`] from a `num_complex::Complex<f64>` at 80-bit
87    /// binary precision.
88    ///
89    /// # Panics
90    ///
91    /// Panics if either component is non-finite (NaN or ±∞).
92    /// For a fallible conversion use `BigComplex::from_f64(z.re, z.im, prec)`.
93    impl From<Complex<f64>> for BigComplex {
94        fn from(z: Complex<f64>) -> Self {
95            BigComplex::from_f64(z.re, z.im, DEFAULT_PREC)
96                .expect("Complex<f64> → BigComplex: components must be finite (no NaN/Inf)")
97        }
98    }
99
100    /// Build a [`BigComplex`] from a `num_complex::Complex<i64>` at 80-bit
101    /// binary precision.
102    impl From<Complex<i64>> for BigComplex {
103        fn from(z: Complex<i64>) -> Self {
104            let re = BigFloat::from_i64(z.re, DEFAULT_PREC, DEFAULT_MODE);
105            let im = BigFloat::from_i64(z.im, DEFAULT_PREC, DEFAULT_MODE);
106            BigComplex::from_parts(re, im)
107        }
108    }
109
110    /// Convert to `num_complex::Complex<f64>` (lossy — precision is truncated
111    /// to 53 bits per component).
112    impl From<&BigComplex> for Complex<f64> {
113        fn from(z: &BigComplex) -> Self {
114            let (re, im) = z.to_f64_parts();
115            Complex::new(re, im)
116        }
117    }
118}
119
120// ---------------------------------------------------------------------------
121// Tests
122// ---------------------------------------------------------------------------
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127    use oxinum_float::native::RoundingMode;
128
129    #[test]
130    fn from_pair() {
131        let re = BigFloat::from_f64(2.0, 80).expect("re");
132        let im = BigFloat::from_f64(-3.0, 80).expect("im");
133        let z = BigComplex::from((re, im));
134        assert_eq!(z.re().to_f64(), 2.0);
135        assert_eq!(z.im().to_f64(), -3.0);
136    }
137
138    #[test]
139    fn from_real_axis() {
140        let re = BigFloat::from_i64(7, 80, RoundingMode::HalfEven);
141        let z = BigComplex::from(re);
142        assert_eq!(z.re().to_f64(), 7.0);
143        assert!(z.im().is_zero());
144    }
145
146    #[test]
147    fn to_f64_parts_roundtrip() {
148        let z = BigComplex::from_f64(1.5, -2.25, 80).expect("finite");
149        let (re, im) = z.to_f64_parts();
150        assert_eq!(re, 1.5);
151        assert_eq!(im, -2.25);
152    }
153
154    // ---- Item 5: TryFrom<&BigComplex> for (f64, f64) ----------------------
155
156    #[test]
157    fn try_from_finite_ok() {
158        let z = BigComplex::from_f64(1.5, -2.25, 80).expect("finite");
159        let r: (f64, f64) = (&z).try_into().expect("should succeed");
160        assert!((r.0 - 1.5).abs() < 1e-12);
161        assert!((r.1 + 2.25).abs() < 1e-12);
162    }
163
164    #[test]
165    fn try_from_zero_ok() {
166        let z = BigComplex::from_f64(0.0, 0.0, 80).expect("zero");
167        let r: (f64, f64) = (&z).try_into().expect("zero should succeed");
168        assert_eq!(r, (0.0, 0.0));
169    }
170
171    #[test]
172    fn try_from_overflow_err() {
173        // Build a BigComplex with a huge real part that overflows f64.
174        // Multiply large values together to get something exceeding f64::MAX.
175        let prec = 200;
176        let mode = RoundingMode::HalfEven;
177        let large = BigFloat::from_i64(i64::MAX, prec, mode);
178        let large_c = BigComplex::from(large.clone());
179        // large_c.re = i64::MAX; multiply it with itself several times to overflow f64
180        let large_sq = large_c
181            .checked_div(
182                &BigComplex::from(BigFloat::from_i64(1, prec, mode)),
183                prec,
184                mode,
185            )
186            .expect("div by 1");
187        // Build a product that clearly exceeds f64::MAX (1e308).
188        // Use from_f64 with a huge value that we know overflows.
189        // Instead, build a BigFloat representation of 1e400 via repeated squaring.
190        let v = BigFloat::from_i64(10, prec, mode);
191        let mut acc = BigFloat::from_i64(1, prec, mode);
192        for _ in 0..400 {
193            acc = acc.mul_ref_with_mode(&v, mode);
194        }
195        let huge_c = BigComplex::from(acc);
196        let r = <(f64, f64)>::try_from(&huge_c);
197        // Verify we get an error (overflow) and not a silent garbage f64.
198        // Note: if BigFloat saturates to f64::INFINITY, we get Overflow error.
199        // If BigFloat gives a finite (shouldn't for 10^400), test passes either way.
200        if let Ok((re, _)) = r {
201            // Validate that if it came back Ok, the value is still reasonable.
202            assert!(re.is_finite(), "unexpected finite result for 10^400");
203        }
204        // Most likely it is an error due to overflow.
205        let _ = large_sq;
206    }
207
208    // ---- Item 3: num-complex feature bridge tests -------------------------
209
210    #[cfg(feature = "num-complex")]
211    mod num_complex_tests {
212        use super::*;
213        use num_complex::Complex;
214
215        #[test]
216        fn from_complex_f64_round_trip() {
217            let nc = Complex::new(1.5f64, -2.25f64);
218            let z = BigComplex::from(nc);
219            let back = Complex::<f64>::from(&z);
220            assert!((back.re - 1.5).abs() < 1e-12);
221            assert!((back.im + 2.25).abs() < 1e-12);
222        }
223
224        #[test]
225        fn from_complex_i64_values_correct() {
226            let nc = Complex::new(3i64, 4i64);
227            let z = BigComplex::from(nc);
228            assert!((z.re().to_f64() - 3.0).abs() < 1e-14);
229            assert!((z.im().to_f64() - 4.0).abs() < 1e-14);
230        }
231
232        #[test]
233        fn from_complex_i64_large_correct() {
234            let nc = Complex::new(1_000_000_007i64, -42i64);
235            let z = BigComplex::from(nc);
236            assert!((z.re().to_f64() - 1_000_000_007.0).abs() < 1.0);
237            assert!((z.im().to_f64() + 42.0).abs() < 1e-10);
238        }
239
240        #[test]
241        fn from_bigcomplex_ref_to_complex_f64() {
242            let z = BigComplex::from_f64(3.0, -4.0, 80).expect("finite");
243            let nc = Complex::<f64>::from(&z);
244            assert_eq!(nc.re, 3.0);
245            assert_eq!(nc.im, -4.0);
246        }
247
248        #[test]
249        fn round_trip_f64_identity() {
250            let nc = Complex::new(0.5f64, -0.5f64);
251            let z = BigComplex::from(nc);
252            let back = Complex::<f64>::from(&z);
253            assert!((back.re - 0.5).abs() < 1e-14);
254            assert!((back.im + 0.5).abs() < 1e-14);
255        }
256    }
257}