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}