Skip to main content

dashu_cmplx/
log.rs

1//! Complex natural logarithm `log(z) = ln|z| + i·arg(z)` (principal branch; cut on `]−∞, 0]`).
2
3use crate::cbig::CBig;
4use crate::repr::{combine_parts, exact, reborrow_cache, riemann, CfpResult, Context};
5use dashu_float::round::Round;
6use dashu_float::{ConstCache, FBig, Repr};
7use dashu_int::Word;
8
9/// Guard digits (base-B) for `log`. Composes `hypot` (for `|z|`), `ln`, and `atan2`.
10const LOG_GUARD: usize = 14;
11
12impl<R: Round> Context<R> {
13    /// Complex natural logarithm under this context (context layer). `log z = ln|z| + i·arg(z)`,
14    /// with the imaginary part in `]−π, π]`. The cache threads into `ln` and `atan2`.
15    ///
16    /// Special values: `log(0) = -∞ + i·0`; `log(±∞) = +∞`; the branch cut on `]−∞, 0]` is handled
17    /// by the signed-zero `atan2` (so `log(-r ± i0) = ln r ± iπ`).
18    pub fn log<const B: Word>(
19        &self,
20        z: &CBig<R, B>,
21        mut cache: Option<&mut ConstCache>,
22    ) -> CfpResult<R, B> {
23        if z.is_zero() {
24            // log(±0) = -∞ + i·arg(±0); arg(0,0) is undefined — report the real -∞ via ln(0)
25            return Ok(exact(
26                FBig::from_repr(Repr::neg_infinity(), self.float()),
27                FBig::from_repr(Repr::zero(), self.float()),
28            ));
29        }
30        if z.is_infinite() {
31            return Ok(riemann(*self)); // log(∞) = +∞ (Riemann point)
32        }
33
34        let gctx = self.guard(LOG_GUARD);
35        let p = self.precision();
36        // ln|z|
37        let r = gctx.hypot(z.re(), z.im())?.value();
38        let ln_r = gctx.ln(r.repr(), reborrow_cache(&mut cache))?.value();
39        // arg(z) = atan2(im, re)
40        let arg = gctx
41            .atan2(z.im(), z.re(), reborrow_cache(&mut cache))?
42            .value();
43        let re = ln_r.with_precision(p);
44        let im = arg.with_precision(p);
45        Ok(combine_parts(re, im))
46    }
47}
48
49impl<R: Round, const B: Word> CBig<R, B> {
50    /// Complex natural logarithm (principal branch; convenience layer).
51    ///
52    /// # Panics
53    ///
54    /// Panics if the precision is unlimited.
55    #[inline]
56    pub fn ln(&self) -> Self {
57        self.context().unwrap_cfp(self.context().log(self, None))
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64    use dashu_base::{Abs, AbsOrd, Sign};
65    use dashu_float::round::mode;
66
67    type C = CBig<mode::HalfAway, 10>;
68    type F = FBig<mode::HalfAway, 10>;
69
70    fn c(re: i32, im: i32) -> C {
71        let mk = |v: i32| -> F { F::from(v).with_precision(53).value() };
72        CBig::from_parts(mk(re), mk(im))
73    }
74
75    fn within(a: &F, b: &F, k: u32) -> bool {
76        if a == b {
77            return true;
78        }
79        let diff = (a.clone() - b.clone()).abs();
80        diff.abs_cmp(&(a.ulp() * F::from(k))).is_le()
81    }
82
83    #[test]
84    fn ln_one_is_zero() {
85        assert!(C::ONE.ln() == C::ZERO);
86    }
87
88    #[test]
89    fn ln_exp_roundtrip() {
90        // ln(exp z) ≈ z (the imaginary 1 sits inside ]-π, π], so no 2πi wrap)
91        let z = c(1, 1);
92        let l = z.exp().ln();
93        let (zr, zi) = z.into_parts();
94        let (lr, li) = l.into_parts();
95        assert!(within(&zr, &lr, 16));
96        assert!(within(&zi, &li, 16));
97    }
98
99    #[test]
100    fn ln_zero_is_neg_infinity() {
101        let l = C::ZERO.ln();
102        assert!(l.re().is_infinite());
103        assert_eq!(l.re().sign(), Sign::Negative);
104    }
105}