Skip to main content

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