num_valid/functions/
exponential.rs

1#![deny(rustdoc::broken_intra_doc_links)]
2
3//! Exponential function implementations.
4//!
5//! This module provides the [`Exp`] trait for computing exponential functions (`e^x`)
6//! for both real and complex numbers.
7
8use crate::{
9    core::policies::StrictFinitePolicy, functions::FunctionErrors, kernels::RawScalarTrait,
10};
11use duplicate::duplicate_item;
12use num::Complex;
13use thiserror::Error;
14use try_create::ValidationPolicy;
15
16//------------------------------------------------------------------------------------------------
17/// Errors that can occur during the input validation phase when attempting to compute
18/// the exponential of a number.
19///
20/// This enum is used as a source for the [`Input`](FunctionErrors::Input) variant of [`ExpErrors`].
21/// It is generic over `RawScalar: RawScalarTrait`, where `RawScalar` is the type of the
22/// number for which the exponential is being computed. The `source` field in the
23/// [`InvalidExponent`](ExpInputErrors::InvalidExponent) variant will be of type
24/// `<RawScalar as RawScalarTrait>::ValidationErrors`.
25#[derive(Debug, Error)]
26pub enum ExpInputErrors<RawScalar: RawScalarTrait> {
27    /// The input exponent failed validation according to the active policy.
28    ///
29    /// This error typically occurs if the input value for the exponential computation
30    /// (the exponent) failed initial validation checks. For example, using
31    /// [`StrictFinitePolicy`], this would
32    /// trigger if the exponent is NaN, Infinity, or (for `f64`) subnormal.
33    #[error("the input exponent is invalid according to validation policy")]
34    // More descriptive
35    InvalidExponent {
36        /// The underlying validation error from the input type.
37        ///
38        /// This provides more specific details about why the input exponent
39        /// was considered invalid by the validation policy. The type of this field
40        /// is `<RawScalar as RawScalarTrait>::ValidationErrors`.
41        #[source]
42        #[backtrace]
43        source: <RawScalar as RawScalarTrait>::ValidationErrors,
44    },
45}
46
47/// Errors that can occur during the computation of the exponential of a real or complex number.
48///
49/// This type represents the possible failures when calling [`Exp::try_exp()`].
50/// It is generic over `RawScalar: RawScalarTrait`. This type alias wraps [`FunctionErrors`],
51/// where the input error source is [`ExpInputErrors<RawScalar>`] and the output
52/// error source is `<RawScalar as RawScalarTrait>::ValidationErrors`.
53///
54/// # Variants
55///
56/// - `Input`: Indicates that the input exponent was invalid for the exponential computation.
57///   This could be due to failing initial validation (e.g., containing NaN or Infinity).
58///   The `source` field provides more specific details via [`ExpInputErrors`].
59///
60/// - `Output`: Indicates that the computed exponential value itself failed validation.
61///   This typically means the result of the `exp` operation yielded a non-finite value
62///   (NaN or Infinity), or overflowed. The `source` field provides details,
63///   usually an instance of [`ErrorsValidationRawReal`](crate::core::errors::ErrorsValidationRawReal)
64///   or [`ErrorsValidationRawComplex`](crate::core::errors::ErrorsValidationRawComplex).
65pub type ExpErrors<RawScalar> =
66    FunctionErrors<ExpInputErrors<RawScalar>, <RawScalar as RawScalarTrait>::ValidationErrors>;
67
68/// A trait for computing the exponential function (`e^x`).
69///
70/// This trait provides an interface for calculating the exponential of a number,
71/// which can be real or complex. It includes both a fallible version (`try_exp`)
72/// that performs validation and an infallible version (`exp`) that may panic
73/// in debug builds if validation fails.
74///
75/// # Implementors
76///
77/// This trait is implemented for:
78/// - `f64`
79/// - `Complex<f64>`
80/// - `RealRugStrictFinite<PRECISION>` (when the `rug` feature is enabled)
81/// - `ComplexRugStrictFinite<PRECISION>` (when the `rug` feature is enabled)
82///
83/// The implementations use a [`StrictFinitePolicy`] for validating inputs and outputs,
84/// meaning that NaN, Infinity, and subnormal numbers (for `f64`) will typically result
85/// in an error or panic.
86pub trait Exp: Sized {
87    /// The error type that can be returned by the `try_exp` method.
88    ///
89    /// This is typically an instantiation of [`ExpErrors`].
90    type Error: std::error::Error;
91
92    /// Attempts to compute the exponential of `self` (`e^self`), returning a `Result`.
93    #[must_use = "this `Result` may contain an error that should be handled"]
94    fn try_exp(self) -> Result<Self, <Self as Exp>::Error>;
95
96    /// Computes and returns the *exponential* of `self`.
97    fn exp(self) -> Self;
98}
99
100#[duplicate_item(
101    T;
102    [f64];
103    [Complex::<f64>];
104)]
105impl Exp for T {
106    type Error = ExpErrors<Self>;
107
108    /// Attempts to compute the exponential of `self` (`e^self`), returning a `Result`.
109    ///
110    /// This method first validates the input `self` using [`StrictFinitePolicy`].
111    /// If the input is valid, it computes the exponential and then validates the result
112    /// using the same policy.
113    ///
114    /// # Returns
115    ///
116    /// - `Ok(Self)`: If both the input and the computed exponential are valid (finite).
117    /// - `Err(Self::Error)`: If the input is invalid (e.g., NaN, Infinity) or if the
118    ///   computed exponential is invalid (e.g., NaN, Infinity, overflow).
119    ///
120    /// # Examples
121    ///
122    /// ```rust
123    /// use num_valid::functions::Exp;
124    /// use num::Complex;
125    ///
126    /// // For f64
127    /// let x = 2.0_f64;
128    /// match x.try_exp() {
129    ///     Ok(val) => println!("e^{} = {}", x, val), // e^2.0 = 7.389...
130    ///     Err(e) => println!("Error: {:?}", e),
131    /// }
132    ///
133    /// assert!(f64::NAN.try_exp().is_err());
134    ///
135    /// // For Complex<f64>
136    /// let z = Complex::new(1.0, std::f64::consts::PI); // e^(1 + iπ) = e * e^(iπ) = e * (-1) = -e
137    /// match z.try_exp() {
138    ///     Ok(val) => println!("e^({:?}) = {:?}", z, val), // e^Complex { re: 1.0, im: 3.14... } = Complex { re: -2.718..., im: tiny }
139    ///     Err(e) => println!("Error: {:?}", e),
140    /// }
141    /// ```
142    #[inline(always)]
143    fn try_exp(self) -> Result<Self, Self::Error> {
144        StrictFinitePolicy::<Self, 53>::validate(self)
145            .map_err(|e| ExpInputErrors::InvalidExponent { source: e }.into())
146            .and_then(|v| {
147                StrictFinitePolicy::<Self, 53>::validate(T::exp(v))
148                    .map_err(|e| Self::Error::Output { source: e })
149            })
150    }
151
152    /// Computes and returns the exponential of `self` (`e^self`).
153    ///
154    /// # Behavior
155    ///
156    /// - **Debug Builds (`#[cfg(debug_assertions)]`)**: This method internally calls `try_exp().unwrap()`.
157    ///   It will panic if the input `self` is invalid (e.g., NaN, Infinity) or if the
158    ///   computed exponential is invalid.
159    /// - **Release Builds (`#[cfg(not(debug_assertions))]`)**: This method calls the underlying
160    ///   exponential function directly (e.g., `f64::exp`, `num::Complex::exp`).
161    ///   The behavior for non-finite inputs or outputs (like NaN propagation or overflow
162    ///   resulting in Infinity) depends on the underlying implementation for the specific type.
163    ///
164    /// # Panics
165    ///
166    /// In debug builds, this method will panic if `try_exp()` would return an `Err`.
167    ///
168    /// # Examples
169    ///
170    /// ```rust
171    /// use num_valid::functions::Exp;
172    /// use num::Complex;
173    ///
174    /// let x = 1.0_f64;
175    /// println!("e^{} = {}", x, x.exp()); // e^1.0 = 2.718...
176    ///
177    /// let z = Complex::new(0.0, std::f64::consts::PI / 2.0); // e^(iπ/2) = i
178    /// println!("e^({:?}) = {:?}", z, z.exp()); // e^Complex { re: 0.0, im: 1.57... } = Complex { re: tiny, im: 1.0 }
179    /// ```
180    #[inline(always)]
181    fn exp(self) -> Self {
182        #[cfg(debug_assertions)]
183        {
184            self.try_exp().unwrap()
185        }
186        #[cfg(not(debug_assertions))]
187        {
188            T::exp(self)
189        }
190    }
191}
192//------------------------------------------------------------------------------------------------
193
194//------------------------------------------------------------------------------------------------
195#[cfg(test)]
196mod tests {
197    use super::*;
198    use num::Complex;
199
200    mod native64 {
201        use super::*;
202
203        mod real {
204            use super::*;
205
206            #[test]
207            fn test_f64_exp_valid() {
208                let value = 4.0;
209                let expected_result = 54.598150033144236;
210                assert_eq!(value.try_exp().unwrap(), expected_result);
211                assert_eq!(value.exp(), expected_result);
212            }
213
214            #[test]
215            fn test_f64_exp_negative() {
216                let value = -4.0;
217                let expected_result = 1.831563888873418e-02;
218                assert_eq!(value.try_exp().unwrap(), expected_result);
219                assert_eq!(value.exp(), expected_result);
220            }
221
222            #[test]
223            fn test_f64_exp_zero() {
224                let value = 0.0;
225                assert_eq!(value.try_exp().unwrap(), 1.0);
226                assert_eq!(value.exp(), 1.0);
227            }
228
229            #[test]
230            fn test_f64_exp_nan() {
231                let value = f64::NAN;
232                let result = value.try_exp();
233                assert!(matches!(result, Err(ExpErrors::<f64>::Input { .. })));
234            }
235
236            #[test]
237            fn test_f64_exp_infinity() {
238                let value = f64::INFINITY;
239                assert!(matches!(
240                    value.try_exp(),
241                    Err(ExpErrors::<f64>::Input { .. })
242                ));
243            }
244
245            #[test]
246            fn test_f64_exp_subnormal() {
247                let value = f64::MIN_POSITIVE / 2.0;
248                assert!(matches!(
249                    value.try_exp(),
250                    Err(ExpErrors::<f64>::Input { .. })
251                ));
252            }
253
254            #[test]
255            fn test_f64_exp_output_overflow() {
256                let value = 710.0; // f64::exp(710.0) is Inf
257                let result = value.try_exp();
258                assert!(matches!(result, Err(ExpErrors::<f64>::Output { .. })));
259            }
260        }
261
262        mod complex {
263            use super::*;
264
265            #[test]
266            fn test_complex_f64_exp_valid() {
267                let value = Complex::new(4.0, 1.0);
268                let expected_result = Complex::new(29.49950635904248, 45.94275907707917);
269                assert_eq!(value.try_exp().unwrap(), expected_result);
270                assert_eq!(value.exp(), expected_result);
271            }
272
273            #[test]
274            fn test_complex_f64_exp_zero() {
275                let value = Complex::new(0.0, 0.0);
276                let expected_result = Complex::new(1.0, 0.0);
277                assert_eq!(value.try_exp().unwrap(), expected_result);
278                assert_eq!(value.exp(), expected_result);
279            }
280
281            #[test]
282            fn test_complex_f64_exp_nan() {
283                let value = Complex::new(f64::NAN, 0.0);
284                assert!(matches!(
285                    value.try_exp(),
286                    Err(ExpErrors::<Complex<f64>>::Input { .. })
287                ));
288
289                let value = Complex::new(0.0, f64::NAN);
290                assert!(matches!(
291                    value.try_exp(),
292                    Err(ExpErrors::<Complex<f64>>::Input { .. })
293                ));
294            }
295
296            #[test]
297            fn test_complex_f64_exp_infinity() {
298                let value = Complex::new(f64::INFINITY, 0.0);
299                assert!(matches!(
300                    value.try_exp(),
301                    Err(ExpErrors::<Complex<f64>>::Input { .. })
302                ));
303
304                let value = Complex::new(0.0, f64::INFINITY);
305                assert!(matches!(
306                    value.try_exp(),
307                    Err(ExpErrors::<Complex<f64>>::Input { .. })
308                ));
309            }
310
311            #[test]
312            fn test_complex_f64_exp_subnormal() {
313                let value = Complex::new(f64::MIN_POSITIVE / 2.0, 0.0);
314                assert!(matches!(
315                    value.try_exp(),
316                    Err(ExpErrors::<Complex<f64>>::Input { .. })
317                ));
318
319                let value = Complex::new(0.0, f64::MIN_POSITIVE / 2.0);
320                assert!(matches!(
321                    value.try_exp(),
322                    Err(ExpErrors::<Complex<f64>>::Input { .. })
323                ));
324            }
325
326            #[test]
327            fn test_complex_f64_exp_output_overflow_real() {
328                let value = Complex::new(710.0, 0.0); // exp(value) has Inf real part
329                let result = value.try_exp();
330                assert!(matches!(
331                    result,
332                    Err(ExpErrors::<Complex<f64>>::Output { .. })
333                ));
334            }
335        }
336    }
337
338    #[cfg(feature = "rug")]
339    mod rug53 {
340        use super::*;
341        use crate::backends::rug::validated::{ComplexRugStrictFinite, RealRugStrictFinite};
342        use try_create::TryNew;
343
344        mod real {
345            use super::*;
346
347            #[test]
348            fn test_rug_float_exp_valid() {
349                let value =
350                    RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, -4.0)).unwrap();
351
352                let expected_result = RealRugStrictFinite::<53>::try_new(rug::Float::with_val(
353                    53,
354                    1.831563888873418e-2,
355                ))
356                .unwrap();
357                assert_eq!(value.clone().try_exp().unwrap(), expected_result);
358                assert_eq!(value.exp(), expected_result);
359            }
360
361            #[test]
362            fn test_rug_float_exp_zero() {
363                let value =
364                    RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 0.0)).unwrap();
365
366                let expected_result =
367                    RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 1.0)).unwrap();
368                assert_eq!(value.clone().try_exp().unwrap(), expected_result);
369                assert_eq!(value.exp(), expected_result);
370            }
371        }
372
373        mod complex {
374            use super::*;
375
376            #[test]
377            fn test_complex_rug_float_exp_valid() {
378                let value = ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
379                    53,
380                    (rug::Float::with_val(53, 4.0), rug::Float::with_val(53, 1.0)),
381                ))
382                .unwrap();
383
384                let expected_result =
385                    ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
386                        53,
387                        (
388                            rug::Float::with_val(53, 29.49950635904248),
389                            rug::Float::with_val(53, 45.94275907707917),
390                        ),
391                    ))
392                    .unwrap();
393                assert_eq!(value.clone().try_exp().unwrap(), expected_result);
394                assert_eq!(value.exp(), expected_result);
395            }
396
397            #[test]
398            fn test_complex_rug_float_exp_zero() {
399                let value = ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
400                    53,
401                    (rug::Float::with_val(53, 0.0), rug::Float::with_val(53, 0.0)),
402                ))
403                .unwrap();
404
405                let expected_result =
406                    ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
407                        53,
408                        (rug::Float::with_val(53, 1.), rug::Float::with_val(53, 0.)),
409                    ))
410                    .unwrap();
411                assert_eq!(value.clone().try_exp().unwrap(), expected_result);
412                assert_eq!(value.exp(), expected_result);
413            }
414        }
415    }
416}
417//------------------------------------------------------------------------------------------------