Skip to main content

dashu_cmplx/
cbig.rs

1//! The [`CBig`] type: an arbitrary-precision complex number.
2//!
3//! [`CBig`] is a pair of real-valued parts (`re`, `im`) over a single shared
4//! [`Context`](crate::Context) — two [`dashu_float::Repr`]s carrying one precision and one rounding
5//! mode. See the [user guide](https://zyxin.xyz/dashu/types.html) for the
6//! layout, and the [compliance notes](https://zyxin.xyz/dashu/compliance.html)
7//! for the C99 Annex G / no-NaN model.
8
9use crate::repr::Context;
10use dashu_base::Sign;
11use dashu_float::round::{mode, Round};
12use dashu_float::{FBig, Repr};
13use dashu_int::Word;
14
15/// An arbitrary-precision complex number with arbitrary base and rounding mode.
16///
17/// The complex number consists of two [`Repr`] parts (the real part `re` and the imaginary part
18/// `im`) over a single shared [`Context`](crate::Context). Each part keeps its own significand
19/// length; the shared context holds the precision cap and rounding mode applied independently to
20/// both components.
21///
22/// # Generic parameters
23///
24/// The const generic parameters are abbreviated as `BASE` -> `B`, `RoundingMode` -> `R`. The `BASE`
25/// must be in range `[2, isize::MAX]`, and the rounding mode `R` is chosen from the
26/// [`dashu_float::round::mode`] module. With the defaults the number is base 2 rounded towards zero
27/// (matching `FBig`'s default).
28///
29/// # Rounding
30///
31/// Each component of a result is rounded independently with the single mode `R`, after the operation
32/// feeds each part enough guard precision. `CBig` follows the C99 Annex G / Kahan model and has no
33/// NaN; see the [compliance guide](https://zyxin.xyz/dashu/compliance.html)
34/// for the full error policy.
35///
36/// # Examples
37///
38/// ```
39/// use dashu_cmplx::CBig;
40/// use dashu_float::{FBig, round::mode::HalfAway};
41///
42/// // base-10 so each integer keeps its own significand
43/// let z = CBig::<HalfAway, 10>::from_parts(FBig::from(3), FBig::from(4));
44/// assert_eq!(z.re().significand(), &3.into());
45/// assert_eq!(z.im().significand(), &4.into());
46/// ```
47pub struct CBig<R: Round = mode::Zero, const B: Word = 2> {
48    pub(crate) re: Repr<B>,
49    pub(crate) im: Repr<B>,
50    pub(crate) context: Context<R>,
51}
52
53impl<R: Round, const B: Word> CBig<R, B> {
54    /// Create a [`CBig`] from raw parts (a `const`-capable constructor, the complex analog of
55    /// [`dashu_float::FBig::from_repr_const`]). Used by the `static_cbig!` literal macro and
56    /// internal code; in most cases prefer [`CBig::from_parts`].
57    #[inline]
58    pub const fn new(re: Repr<B>, im: Repr<B>, context: Context<R>) -> Self {
59        Self { re, im, context }
60    }
61
62    /// Create a [`CBig`] from its real and imaginary parts.
63    ///
64    /// The result context is `max(re.context(), im.context())` (the larger precision wins; an
65    /// unlimited `0` precision is treated as the minimum, so a limited operand's precision wins),
66    /// and the smaller-precision part is effectively widened to it — widening is exact, so only the
67    /// precision cap changes. The rounding mode and base must match and are enforced by the type
68    /// parameters.
69    ///
70    /// # Examples
71    ///
72    /// ```
73    /// use dashu_cmplx::CBig;
74    /// use dashu_float::{FBig, round::mode::HalfAway};
75    ///
76    /// type C = CBig<HalfAway, 10>;
77    /// type F = FBig<HalfAway, 10>;
78    /// let z = C::from_parts(F::from(3), F::from(4));
79    /// let (re, im) = z.into_parts();
80    /// assert_eq!(re, F::from(3));
81    /// assert_eq!(im, F::from(4));
82    /// ```
83    #[inline]
84    pub fn from_parts(re: FBig<R, B>, im: FBig<R, B>) -> Self {
85        let fctx = dashu_float::Context::max(re.context(), im.context());
86        Self {
87            re: re.into_repr(),
88            im: im.into_repr(),
89            context: Context(fctx),
90        }
91    }
92
93    /// The complex number zero `0 + 0i` (unlimited precision).
94    pub const ZERO: Self = Self::new(Repr::zero(), Repr::zero(), Context::new(0));
95
96    /// The complex number one `1 + 0i` (unlimited precision).
97    pub const ONE: Self = Self::new(Repr::one(), Repr::zero(), Context::new(0));
98
99    /// The complex number negative one `-1 + 0i` (unlimited precision).
100    pub const NEG_ONE: Self = Self::new(Repr::neg_one(), Repr::zero(), Context::new(0));
101
102    /// The imaginary unit `0 + 1i` (unlimited precision).
103    pub const I: Self = Self::new(Repr::zero(), Repr::one(), Context::new(0));
104
105    /// Get the shared [`Context`](crate::Context) of the complex number.
106    #[inline]
107    pub const fn context(&self) -> Context<R> {
108        self.context
109    }
110
111    /// Get the precision limit of the complex number (`0` = unlimited). Both parts share it.
112    #[inline]
113    pub const fn precision(&self) -> usize {
114        self.context.precision()
115    }
116
117    /// Get a reference to the real part's raw representation.
118    #[inline]
119    pub const fn re(&self) -> &Repr<B> {
120        &self.re
121    }
122
123    /// Get a reference to the imaginary part's raw representation.
124    #[inline]
125    pub const fn im(&self) -> &Repr<B> {
126        &self.im
127    }
128
129    /// Convert the complex number into its real and imaginary parts as [`FBig`]s, each carrying the
130    /// (copied) shared context — zero clone of the significands.
131    #[inline]
132    pub fn into_parts(self) -> (FBig<R, B>, FBig<R, B>) {
133        let fctx = self.context.float();
134        (FBig::from_repr(self.re, fctx), FBig::from_repr(self.im, fctx))
135    }
136
137    /// Determine if the complex number is numerically zero (both parts `±0`).
138    #[inline]
139    pub fn is_zero(&self) -> bool {
140        (self.re.is_pos_zero() || self.re.is_neg_zero())
141            && (self.im.is_pos_zero() || self.im.is_neg_zero())
142    }
143
144    /// Determine if either part of the complex number is infinite.
145    #[inline]
146    pub fn is_infinite(&self) -> bool {
147        self.re.is_infinite() || self.im.is_infinite()
148    }
149
150    /// Determine if the complex number is finite (neither part infinite).
151    #[inline]
152    pub fn is_finite(&self) -> bool {
153        !self.is_infinite()
154    }
155
156    /// The complex infinity produced on overflow: the single Riemann point `+∞ + i·0`, matching
157    /// [`riemann`](crate::repr) and `proj` so overflow-produced infinities compare equal to the
158    /// special-value paths' infinities.
159    #[inline]
160    pub(crate) fn overflow(context: &Context<R>, _sign: Sign) -> Self {
161        Self::new(Repr::infinity(), Repr::zero(), *context)
162    }
163
164    /// The complex zero produced on underflow: a signed zero on the real part and `+0` imaginary.
165    #[inline]
166    pub(crate) fn underflow(context: &Context<R>, sign: Sign) -> Self {
167        let re = match sign {
168            Sign::Positive => Repr::zero(),
169            Sign::Negative => Repr::neg_zero(),
170        };
171        Self::new(re, Repr::zero(), *context)
172    }
173}
174
175// Custom Clone (the significands are heap-allocated), mirroring FBig.
176impl<R: Round, const B: Word> Clone for CBig<R, B> {
177    #[inline]
178    fn clone(&self) -> Self {
179        Self {
180            re: self.re.clone(),
181            im: self.im.clone(),
182            context: self.context,
183        }
184    }
185}
186
187impl<R: Round, const B: Word> Default for CBig<R, B> {
188    /// Default value: `0 + 0i`.
189    #[inline]
190    fn default() -> Self {
191        Self::ZERO
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198
199    type C = CBig<mode::HalfAway, 10>;
200
201    #[test]
202    fn constants() {
203        assert!(C::ZERO.is_zero());
204        assert!(!C::ONE.is_zero());
205        assert!(!C::NEG_ONE.is_zero());
206        assert!(C::NEG_ONE != C::ONE);
207        assert!(!C::I.is_zero());
208        let (re, im) = C::I.into_parts();
209        assert!(re.repr().is_pos_zero());
210        assert!(im.repr().is_one());
211    }
212
213    #[test]
214    fn from_parts_reconciles_precision() {
215        type F = FBig<mode::HalfAway, 10>;
216        let re = F::from_parts(3.into(), 0); // precision 1 (one decimal digit)
217        let im = F::from_parts(4.into(), 0); // precision 1
218        let z = CBig::from_parts(re, im);
219        assert_eq!(z.precision(), 1);
220        assert_eq!(z.re().significand(), &3.into());
221        assert_eq!(z.im().significand(), &4.into());
222    }
223
224    #[test]
225    fn predicates() {
226        let inf = C::new(Repr::infinity(), Repr::zero(), Context::new(0));
227        assert!(inf.is_infinite());
228        assert!(!inf.is_finite());
229        assert!(!inf.is_zero());
230
231        // a finite, nonzero number
232        let z = C::from_parts(FBig::from(3), FBig::from(0));
233        assert!(!z.is_infinite());
234        assert!(z.is_finite());
235        assert!(!z.is_zero());
236
237        // both parts zero (incl. -0)
238        let neg_zero = C::new(Repr::neg_zero(), Repr::zero(), Context::new(0));
239        assert!(neg_zero.is_zero());
240    }
241}