dashu_cmplx/repr.rs
1//! The complex [`Context`] and the result/inexactness types.
2//!
3//! Mirroring `dashu-float`'s `repr.rs` (which hosts both `Repr` and `Context`), the complex
4//! [`Context`] lives here. `dashu-cmplx` reuses `dashu-float`'s [`Repr`] unchanged, so unlike float's
5//! module this one holds only the complex-side pieces: the [`Context`] newtype and the
6//! [`CfpResult`]/[`CRounded`] result types.
7//!
8//! [`Context`] is a thin newtype around [`dashu_float::Context`] that hosts the context-layer
9//! CBig operations (it can't be added to `FBig`'s own `Context` from this crate — coherence). The
10//! wrapped value *is* the shared precision/rounding config, so the config API
11//! ([`Context::new`] / [`Context::max`] / [`Context::precision`]) just delegates to the inner float
12//! context.
13//!
14//! The complex analog of `FpResult`/`Rounded` is [`CfpResult`]/[`CRounded`]: a complex result
15//! carries **two** inexactness flags (one per axis), modeled as
16//! `Approximation<CBig, (Rounding, Rounding)>`.
17
18use dashu_base::Approximation;
19use dashu_base::Approximation::*;
20use dashu_float::round::{Round, Rounding};
21use dashu_float::{ConstCache, Context as FloatCtxt, FBig, FpError, Repr};
22use dashu_int::Word;
23
24use crate::cbig::CBig;
25
26/// CBig operation context — a newtype wrapper around [`dashu_float::Context`], and also the type
27/// stored on each [`CBig`] as its shared precision/rounding config (so [`CBig::context`] returns it
28/// directly, with no wrapping).
29///
30/// It is a separate type because inherent methods cannot be added to `FBig`'s `Context` from this
31/// crate; it exists to host the context-layer CBig operations ([`Context::mul`], [`Context::exp`], …).
32/// The config API just delegates inward to the wrapped float context.
33#[derive(Clone, Copy)]
34pub struct Context<R: Round>(pub(crate) FloatCtxt<R>);
35
36/// Correctly-rounded complex result with per-axis inexactness.
37///
38/// `Exact(v)` ⟺ both parts are exact; `Inexact(v, (re, im))` carries each part's rounding
39/// direction. This is the complex twin of [`dashu_float::round::Rounded`] (`Approximation<T, Rounding>`),
40/// reusing the same [`Rounding`] flag type for each axis.
41pub type CRounded<R, const B: Word> = Approximation<CBig<R, B>, (Rounding, Rounding)>;
42
43/// The result of a context-layer CBig operation: a correctly-rounded [`CBig`] (with per-axis
44/// inexactness) or an [`FpError`]. The complex analog of [`dashu_float::FpResult`].
45pub type CfpResult<R, const B: Word> = Result<CRounded<R, B>, FpError>;
46
47impl<R: Round> Context<R> {
48 /// Create a CBig operation context with the given precision limit (`0` = unlimited).
49 #[inline]
50 pub const fn new(precision: usize) -> Self {
51 Self(FloatCtxt::new(precision))
52 }
53
54 /// Create a context with the higher precision from the two inputs (unlimited `0` dominates).
55 #[inline]
56 pub const fn max(lhs: Self, rhs: Self) -> Self {
57 Self(FloatCtxt::max(lhs.0, rhs.0))
58 }
59
60 /// The precision limit stored in the context (`0` = unlimited). Both parts of a [`CBig`] always
61 /// share this single precision.
62 #[inline]
63 pub const fn precision(&self) -> usize {
64 self.0.precision()
65 }
66
67 /// The inner float context used to drive the real-part math (copied, since it is `Copy`).
68 #[inline]
69 pub(crate) const fn float(&self) -> FloatCtxt<R> {
70 self.0
71 }
72
73 /// Build a transient float working context at `p + g` guard digits — the guard-digit recipe
74 /// (§6.1 of the design doc) evaluates each component at extra precision and re-rounds to `p`.
75 #[inline]
76 pub(crate) fn guard(&self, g: usize) -> FloatCtxt<R> {
77 FloatCtxt::new(self.precision() + g)
78 }
79
80 /// Unwrap a [`CfpResult`], returning the [`CBig`] value directly.
81 ///
82 /// The complex analog of [`dashu_float::Context::unwrap_fp`]. It drops the per-axis
83 /// `(Rounding, Rounding)` flags, and applies the same error policy: [`FpError::Overflow`]
84 /// saturates to a signed infinity, [`FpError::Underflow`] to a signed zero, and the remaining
85 /// variants panic.
86 #[inline]
87 pub fn unwrap_cfp<const B: Word>(&self, result: CfpResult<R, B>) -> CBig<R, B> {
88 match result {
89 Ok(rounded) => rounded.value(),
90 Err(FpError::Overflow(sign)) => CBig::overflow(self, sign),
91 Err(FpError::Underflow(sign)) => CBig::underflow(self, sign),
92 Err(FpError::InfiniteInput) => {
93 panic!("arithmetic operations with the infinity are not allowed!")
94 }
95 Err(FpError::OutOfDomain) => panic!("the operation result is out of domain!"),
96 Err(FpError::Indeterminate) => {
97 panic!("the result of the operation is an indeterminate form!")
98 }
99 }
100 }
101}
102
103/// Combine two per-part float rounding results into a [`CRounded`] complex result, carrying each
104/// part's inexactness flag. `Exact` iff both parts are exact.
105pub(crate) fn combine_parts<R: Round, const B: Word>(
106 re: Approximation<FBig<R, B>, Rounding>,
107 im: Approximation<FBig<R, B>, Rounding>,
108) -> CRounded<R, B> {
109 let (re_val, re_rnd) = match re {
110 Approximation::Exact(v) => (v, Rounding::NoOp),
111 Approximation::Inexact(v, r) => (v, r),
112 };
113 let (im_val, im_rnd) = match im {
114 Approximation::Exact(v) => (v, Rounding::NoOp),
115 Approximation::Inexact(v, r) => (v, r),
116 };
117 let value = CBig::from_parts(re_val, im_val);
118 if re_rnd == Rounding::NoOp && im_rnd == Rounding::NoOp {
119 Exact(value)
120 } else {
121 Inexact(value, (re_rnd, im_rnd))
122 }
123}
124
125/// Build a [`CRounded`] from two already-unwrapped float results, when the per-part rounding flags
126/// are known directly (used by the exact/short-circuit special-value paths).
127pub(crate) fn exact<R: Round, const B: Word>(re: FBig<R, B>, im: FBig<R, B>) -> CRounded<R, B> {
128 Exact(CBig::from_parts(re, im))
129}
130
131/// The Riemann point at infinity `+∞ + i·0` as an exact [`CRounded`] result (dashu's complex
132/// infinity — the single point `proj` collapses any infinity to).
133pub(crate) fn riemann<R: Round, const B: Word>(context: Context<R>) -> CRounded<R, B> {
134 exact(
135 FBig::from_repr(Repr::infinity(), context.float()),
136 FBig::from_repr(Repr::zero(), context.float()),
137 )
138}
139
140/// Reborrow an `Option<&mut ConstCache>` for a sequential sub-call (mirrors `dashu-float`'s
141/// `reborrow_cache`; `as_deref_mut` is the natural reborrow, allowed here centrally).
142#[inline]
143#[allow(clippy::needless_option_as_deref)]
144pub(crate) fn reborrow_cache<'a>(
145 cache: &'a mut Option<&mut ConstCache>,
146) -> Option<&'a mut ConstCache> {
147 cache.as_deref_mut()
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 use dashu_base::Sign;
154 use dashu_float::round::mode;
155 use dashu_float::Repr;
156
157 #[test]
158 fn context_delegates_to_float() {
159 let ctx: Context<mode::HalfEven> = Context::new(53);
160 assert_eq!(ctx.precision(), 53);
161 let bigger = Context::max(ctx, Context::new(10));
162 assert_eq!(bigger.precision(), 53);
163 // unlimited (0) is treated as the minimum precision, so a limited operand wins
164 let limited_wins = Context::max(ctx, Context::new(0));
165 assert_eq!(limited_wins.precision(), 53);
166 let both_unlimited = Context::max(Context::<mode::HalfEven>::new(0), Context::new(0));
167 assert_eq!(both_unlimited.precision(), 0);
168 }
169
170 #[test]
171 fn combine_parts_exact_and_inexact() {
172 let ctx: Context<mode::HalfEven> = Context::new(10);
173 let f = ctx.float();
174 let one = FBig::from_repr(Repr::<2>::one(), f);
175 // two exact results combine to Exact
176 let combined = combine_parts(Exact(one.clone()), Exact(one.clone()));
177 assert!(matches!(combined, Exact(_)));
178 // an inexact result combines to Inexact
179 let add_one = f.add(one.repr(), one.repr()).unwrap(); // 1+1, exact at p=10
180 let combined2 = combine_parts(add_one, Inexact(one, Rounding::AddOne));
181 assert!(matches!(combined2, Inexact(_, _)));
182 let _ = Sign::Positive; // keep Sign referenced
183 }
184}