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