num_valid/functions/
exponential.rs

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