diff --git a/src/complex_float.rs b/src/complex_float.rs index 873fe73..5b1c143 100644 --- a/src/complex_float.rs +++ b/src/complex_float.rs @@ -144,6 +144,22 @@ pub trait ComplexFloat: Num + NumCast + Copy + Neg + private::Sea /// /// Formula: `a+bi -> a-bi` fn conj(self) -> Self; + + /// Returns ln(1+n) (natural logarithm) more accurately + /// than if the operations were performed separately + /// + /// Formula: ln(1+z) + /// + /// where z = a+bi + fn ln_1p(self) -> Self; + + /// Returns e^(self) - 1 in a way that is accurate + /// even if the number is close to zero + /// + /// Formaula: e^(z) - 1 + /// + /// where z = a+bi + fn exp_m1(self) -> Self; } macro_rules! forward { @@ -235,6 +251,8 @@ where Float::acosh(self) -> Self; Float::atanh(self) -> Self; Float::abs(self) -> Self; + Float::ln_1p(self) -> Self; + Float::exp_m1(self) -> Self; } } @@ -306,6 +324,8 @@ impl ComplexFloat for Complex { Complex::asinh(self) -> Self; Complex::acosh(self) -> Self; Complex::atanh(self) -> Self; + Complex::ln_1p(self) -> Self; + Complex::exp_m1(self) -> Self; } forward_ref! { diff --git a/src/lib.rs b/src/lib.rs index 661b67b..3e3732c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -624,6 +624,48 @@ impl Complex { pub fn fdiv(self, other: Complex) -> Complex { self * other.finv() } + + /// The ln_1p() function computes the natural logarithm + /// of (1 + z), which is particularly useful for + /// small values of `z` to avoid loss of precision + /// + /// # Examples + /// + /// ```rust + /// use num_complex::Complex64; + /// use num_complex::ComplexFloat; + /// + /// let a = Complex64::new(2.0e-16, 3.0e-16); + /// + /// let approx_val = a.ln_1p(); + /// let expected_val = Complex64::new(2.2204e-16, 2.999e-16); + /// assert!((approx_val - expected_val).norm() < 1e-18); + /// ``` + #[inline] + pub fn ln_1p(self) -> Self { + (Self::one() + self).ln() + } + + /// The exp_m1() function computes the exponential + /// of z minus one (`e^(z) - 1`), which is particularly + /// useful for small values of `z` to avoid loss of precision + /// + /// # Examples + /// + /// ```rust + /// use num_complex::Complex64; + /// use num_complex::ComplexFloat; + /// + /// let a = Complex64::new(2.0e-16, 3.0e-16); + /// + /// let approx_val = a.exp_m1(); + /// let expected_val = Complex64::new(2.2204e-16, 3.0e-16); + /// assert!((approx_val - expected_val).norm() < 1e-18); + /// ``` + #[inline] + pub fn exp_m1(self) -> Self { + self.exp() - Self::one() + } } #[cfg(any(feature = "std", feature = "libm"))] @@ -2432,6 +2474,22 @@ pub(crate) mod test { )); } } + + #[test] + fn test_ln_1p() { + for &c in all_consts.iter() { + // ln_1p(z) = ln(1+z) + assert!(close(c.ln_1p(), (1.0 + c).ln())); + } + } + + #[test] + fn test_exp_m1() { + for &c in all_consts.iter() { + // exp_m1(z) = exp(z) - 1 + assert!(close(c.exp_m1(), (c).exp() - 1.0)); + } + } } // Test both a + b and a += b