Skip to main content

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}