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