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//------------------------------------------------------------------------------------------------