Skip to main content

dashu_cmplx/third_party/
num_complex.rs

1//! Conversions between [`CBig`] and `num-complex`'s `Complex<f32>`/`Complex<f64>` (behind the
2//! `num-complex` feature).
3//!
4//! Mirroring `dashu-float`'s primitive-float conversions, both directions are restricted to base 2
5//! and compose through [`FBig`]: a `Complex<f64>` splits into two `f64`s, each lifted to an exact
6//! base-2 [`FBig`] (NaN → [`ConversionError::OutOfBounds`]; infinities and signed zeros preserved);
7//! a base-2 [`CBig`] rounds each part back to `f32`/`f64`, erroring on overflow or inexactness. The
8//! pair is therefore the exact-into / rounding-out-of split that [`FBig`] uses for `f64`.
9
10use crate::cbig::CBig;
11use dashu_base::ConversionError;
12use dashu_float::round::Round;
13use dashu_float::FBig;
14use num_complex_v04::{Complex32, Complex64};
15
16macro_rules! impl_complex_conversions {
17    ($cx:ty, $f:ty) => {
18        impl<R: Round> TryFrom<$cx> for CBig<R, 2> {
19            type Error = ConversionError;
20
21            /// Lift a primitive-float `Complex` to an exact base-2 [`CBig`]. A `NaN` part is
22            /// unmappable (`CBig` has no NaN) and yields [`ConversionError::OutOfBounds`];
23            /// infinities and signed zeros are preserved, exactly as for `FBig: TryFrom<f64>`.
24            #[inline]
25            fn try_from(c: $cx) -> Result<Self, Self::Error> {
26                let re = FBig::try_from(c.re)?;
27                let im = FBig::try_from(c.im)?;
28                Ok(CBig::from_parts(re, im))
29            }
30        }
31
32        impl<R: Round> TryFrom<CBig<R, 2>> for $cx {
33            type Error = ConversionError;
34
35            /// Round a base-2 [`CBig`] back to a primitive-float `Complex`, composing
36            /// [`CBig`] → [`FBig`] → `f32`/`f64` per part. Errors on overflow or inexactness, and
37            /// also when a part is already infinite — mirroring `FBig: TryFrom<FBig> for f64`.
38            #[inline]
39            fn try_from(z: CBig<R, 2>) -> Result<Self, Self::Error> {
40                let fctx = z.context.float();
41                let re = <$f>::try_from(FBig::from_repr(z.re, fctx))?;
42                let im = <$f>::try_from(FBig::from_repr(z.im, fctx))?;
43                Ok(<$cx>::new(re, im))
44            }
45        }
46    };
47}
48
49impl_complex_conversions!(Complex32, f32);
50impl_complex_conversions!(Complex64, f64);
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use dashu_float::round::mode;
56
57    type C2 = CBig<mode::Zero, 2>;
58
59    #[test]
60    fn f64_roundtrip() {
61        for (re, im) in [
62            (3.0_f64, 4.0),
63            (1.0, 0.0),
64            (0.0, 1.0),
65            (-2.0, 0.5),
66            (0.0, 0.0),
67            (1.5, -2.25),
68        ] {
69            let c = Complex64::new(re, im);
70            let z = C2::try_from(c).unwrap();
71            let back: Complex64 = z.try_into().unwrap();
72            assert_eq!(back, c, "roundtrip failed for {re}+{im}i");
73        }
74    }
75
76    #[test]
77    fn f32_roundtrip() {
78        for (re, im) in [(3.0_f32, 4.0), (-1.5_f32, 0.25)] {
79            let c = Complex32::new(re, im);
80            let z = C2::try_from(c).unwrap();
81            let back: Complex32 = z.try_into().unwrap();
82            assert_eq!(back, c);
83        }
84    }
85
86    #[test]
87    fn nan_is_out_of_bounds() {
88        assert_eq!(C2::try_from(Complex64::new(f64::NAN, 0.0)), Err(ConversionError::OutOfBounds));
89        assert_eq!(C2::try_from(Complex64::new(0.0, f64::NAN)), Err(ConversionError::OutOfBounds));
90    }
91
92    #[test]
93    fn infinities_preserved_on_lift() {
94        let z = C2::try_from(Complex64::new(f64::INFINITY, f64::NEG_INFINITY)).unwrap();
95        assert!(z.re().is_infinite());
96        assert!(z.im().is_infinite());
97        // an infinite part can't round-trip back to f64 (mirrors FBig)
98        assert_eq!(Complex64::try_from(z), Err(ConversionError::LossOfPrecision));
99    }
100
101    #[test]
102    fn signed_zero_preserved() {
103        let z = C2::try_from(Complex64::new(-0.0, 0.0)).unwrap();
104        assert!(z.re().is_neg_zero());
105        assert!(z.im().is_pos_zero());
106    }
107
108    #[test]
109    fn high_precision_rounds_inexactly() {
110        // 2^53 + 1 needs 54 mantissa bits, so it can't convert back to f64 exactly
111        let big = FBig::<mode::Zero, 2>::from_parts(((1u64 << 53) + 1).into(), 0);
112        let z = C2::from_parts(big, FBig::from(0));
113        assert_eq!(Complex64::try_from(z), Err(ConversionError::LossOfPrecision));
114    }
115}