Skip to main content

dashu_cmplx/
convert.rs

1//! Conversions between [`CBig`], [`FBig`], and integers.
2//!
3//! The into-`CBig` direction is lossless through [`From`] (a real [`FBig`], or a `UBig`/`IBig` →
4//! the real part, with imaginary `+0`). The out-of-`CBig` direction is lossy through [`TryFrom`],
5//! composing `CBig → FBig → IBig` — exactly the [`From`]/[`TryFrom`] split `FBig` uses.
6
7use crate::cbig::CBig;
8use dashu_base::ConversionError;
9use dashu_float::round::Round;
10use dashu_float::{FBig, Repr};
11use dashu_int::{IBig, UBig, Word};
12
13impl<R: Round, const B: Word> From<FBig<R, B>> for CBig<R, B> {
14    /// Embed a real [`FBig`] as a complex number with imaginary part `+0`.
15    #[inline]
16    fn from(re: FBig<R, B>) -> Self {
17        let fctx = re.context();
18        Self {
19            re: re.into_repr(),
20            im: Repr::zero(),
21            context: crate::repr::Context(fctx),
22        }
23    }
24}
25
26impl<R: Round, const B: Word> From<UBig> for CBig<R, B> {
27    /// Embed an unsigned integer as a complex number (exact, unlimited precision) with imaginary `+0`.
28    #[inline]
29    fn from(v: UBig) -> Self {
30        FBig::from(v).into()
31    }
32}
33
34impl<R: Round, const B: Word> From<IBig> for CBig<R, B> {
35    /// Embed a signed integer as a complex number (exact, unlimited precision) with imaginary `+0`.
36    #[inline]
37    fn from(v: IBig) -> Self {
38        FBig::from(v).into()
39    }
40}
41
42impl<R: Round, const B: Word> TryFrom<CBig<R, B>> for FBig<R, B> {
43    type Error = ConversionError;
44
45    /// Extract the real part, succeeding only when the imaginary part is zero (purely real; both
46    /// `±0` count as zero). This is the guarded "is this complex actually real?" check — distinct
47    /// from [`CBig::re`] / [`CBig::into_parts`], which return the real part unconditionally.
48    #[inline]
49    fn try_from(z: CBig<R, B>) -> Result<Self, Self::Error> {
50        if z.im.is_pos_zero() || z.im.is_neg_zero() {
51            Ok(FBig::from_repr(z.re, z.context.float()))
52        } else {
53            Err(ConversionError::LossOfPrecision)
54        }
55    }
56}
57
58impl<R: Round, const B: Word> TryFrom<CBig<R, B>> for IBig {
59    type Error = ConversionError;
60
61    /// Extract an integer, succeeding only when the number is purely real, finite, and
62    /// integer-valued. Composes [`CBig`] → [`FBig`] → [`IBig`]; for a rounding-aware path use
63    /// [`FBig::to_int`] on the real part ([`CBig::re`]).
64    #[inline]
65    fn try_from(z: CBig<R, B>) -> Result<Self, Self::Error> {
66        let re: FBig<R, B> = FBig::try_from(z)?;
67        IBig::try_from(re)
68    }
69}
70
71impl<R: Round, const B: Word> TryFrom<CBig<R, B>> for UBig {
72    type Error = ConversionError;
73
74    /// Extract an unsigned integer, succeeding only when the number is purely real, finite,
75    /// integer-valued, and non-negative. Composes [`CBig`] → [`FBig`] → [`UBig`].
76    #[inline]
77    fn try_from(z: CBig<R, B>) -> Result<Self, Self::Error> {
78        let re: FBig<R, B> = FBig::try_from(z)?;
79        UBig::try_from(re)
80    }
81}
82
83// Conversions between `CBig` and the integer primitives (both directions), composing through
84// `FBig`. Integers embed as purely-real complex numbers; extraction succeeds only when the value
85// is purely real, finite, in range, and integer-valued.
86macro_rules! impl_cbig_int_conv {
87    ($($t:ty)*) => {$(
88        impl<R: Round, const B: Word> From<$t> for CBig<R, B> {
89            #[inline]
90            fn from(v: $t) -> Self {
91                FBig::from(v).into()
92            }
93        }
94
95        impl<R: Round, const B: Word> TryFrom<CBig<R, B>> for $t {
96            type Error = ConversionError;
97
98            #[inline]
99            fn try_from(z: CBig<R, B>) -> Result<Self, Self::Error> {
100                let re: FBig<R, B> = FBig::try_from(z)?;
101                re.try_into()
102            }
103        }
104    )*};
105}
106impl_cbig_int_conv!(u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize);
107
108// Conversions between `CBig` and `f32`/`f64` (both directions, base 2 only). NaN is rejected on
109// input; infinities are preserved. Extraction succeeds only when purely real and exactly
110// representable.
111macro_rules! impl_cbig_float_conv {
112    ($($t:ty)*) => {$(
113        impl<R: Round> TryFrom<$t> for CBig<R, 2> {
114            type Error = ConversionError;
115
116            #[inline]
117            fn try_from(f: $t) -> Result<Self, Self::Error> {
118                Ok(CBig::from(FBig::try_from(f)?))
119            }
120        }
121
122        impl<R: Round> TryFrom<CBig<R, 2>> for $t {
123            type Error = ConversionError;
124
125            #[inline]
126            fn try_from(z: CBig<R, 2>) -> Result<Self, Self::Error> {
127                let re: FBig<R, 2> = FBig::try_from(z)?;
128                re.try_into()
129            }
130        }
131    )*};
132}
133impl_cbig_float_conv!(f32 f64);
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138    use dashu_float::round::mode;
139
140    type C = CBig<mode::HalfAway, 10>;
141    type F = FBig<mode::HalfAway, 10>;
142
143    #[test]
144    fn from_fbig_is_purely_real() {
145        let z = C::from(F::from(7));
146        assert!(z.im().is_pos_zero() || z.im().is_neg_zero());
147        assert_eq!(z.re().significand(), &7.into());
148    }
149
150    #[test]
151    fn from_integers() {
152        let z: C = UBig::from(5u32).into();
153        assert_eq!(z.re().significand(), &5.into());
154        let z: C = IBig::from(-3).into();
155        assert_eq!(z.re().significand(), &(-3i32).into());
156    }
157
158    #[test]
159    fn try_from_fbig_ok_iff_purely_real() {
160        let z = C::from_parts(7.into(), 0.into());
161        let re: F = F::try_from(z).unwrap();
162        assert_eq!(re.repr().significand(), &7.into());
163
164        let z = C::from_parts(3.into(), 4.into());
165        assert_eq!(F::try_from(z), Err(ConversionError::LossOfPrecision));
166    }
167
168    #[test]
169    fn try_from_ibig_composes() {
170        let z: C = IBig::from(9).into();
171        let i: IBig = IBig::try_from(z).unwrap();
172        assert_eq!(i, 9.into());
173
174        // fractional real part → LossOfPrecision
175        let z = C::from(F::from_parts(123.into(), -2)); // 1.23
176        assert_eq!(IBig::try_from(z), Err(ConversionError::LossOfPrecision));
177
178        // nonzero imaginary → LossOfPrecision
179        let z = C::from_parts(9.into(), 1.into());
180        assert_eq!(IBig::try_from(z), Err(ConversionError::LossOfPrecision));
181    }
182
183    #[test]
184    fn try_from_ubig_composes() {
185        let z: C = IBig::from(9).into();
186        let u: UBig = UBig::try_from(z).unwrap();
187        assert_eq!(u, UBig::from(9u8));
188
189        // negative real part → OutOfBounds
190        let z: C = IBig::from(-9).into();
191        assert_eq!(UBig::try_from(z), Err(ConversionError::OutOfBounds));
192
193        // fractional real part → LossOfPrecision
194        let z = C::from(F::from_parts(123.into(), -2)); // 1.23
195        assert_eq!(UBig::try_from(z), Err(ConversionError::LossOfPrecision));
196
197        // nonzero imaginary → LossOfPrecision
198        let z = C::from_parts(9.into(), 1.into());
199        assert_eq!(UBig::try_from(z), Err(ConversionError::LossOfPrecision));
200    }
201
202    #[test]
203    fn primitive_conversions() {
204        // integers embed as purely-real complex numbers (any base)
205        let z: C = 7u8.into();
206        assert_eq!(z.re().significand(), &7.into());
207        let z: C = (-3i8).into();
208        assert_eq!(z.re().significand(), &(-3i32).into());
209
210        // floats embed into a base-2 CBig; NaN is rejected
211        let z = CBig::<mode::HalfAway, 2>::try_from(2.5f64).unwrap();
212        assert_eq!(z.re().significand(), &5.into()); // 2.5 = 5 * 2^-1
213        assert!(CBig::<mode::HalfAway, 2>::try_from(f32::NAN).is_err());
214
215        // CBig -> integer primitive (fails on negative / out-of-range / fractional / nonzero-imag)
216        assert_eq!(u8::try_from(C::from(9u8)), Ok(9u8));
217        assert_eq!(u8::try_from(C::from(-9i8)), Err(ConversionError::OutOfBounds));
218        assert_eq!(u8::try_from(C::from(300u16)), Err(ConversionError::OutOfBounds));
219        assert_eq!(i8::try_from(C::from(-9i8)), Ok(-9i8));
220        assert_eq!(
221            i8::try_from(C::from(F::from_parts(123.into(), -2))), // 1.23
222            Err(ConversionError::LossOfPrecision)
223        );
224
225        // CBig -> float primitive (base-2 only)
226        let z = CBig::<mode::HalfAway, 2>::try_from(2.5f64).unwrap();
227        assert_eq!(f64::try_from(z), Ok(2.5));
228    }
229}