num_valid/functions/
reciprocal.rs

1#![deny(rustdoc::broken_intra_doc_links)]
2
3use crate::{
4    functions::FunctionErrors,
5    kernels::RawScalarTrait,
6    validation::{StrictFinitePolicy, capture_backtrace},
7};
8use duplicate::duplicate_item;
9use num::Complex;
10use std::backtrace::Backtrace;
11use thiserror::Error;
12use try_create::ValidationPolicy;
13
14//------------------------------------------------------------------------------------------------
15/// Errors that can occur during the input validation phase or due to special input
16/// values when attempting to compute the reciprocal of a number.
17///
18/// This enum is used as a source for the [`Input`](FunctionErrors::Input) variant of [`ReciprocalErrors`].
19/// It is generic over `RawScalar: RawScalarTrait`. The [`InvalidArgument`](ReciprocalInputErrors::InvalidArgument)
20/// variant contains a `source` of type `<RawScalar as RawScalarTrait>::ValidationErrors`.
21#[derive(Debug, Error)]
22pub enum ReciprocalInputErrors<RawScalar: RawScalarTrait> {
23    /// The input value is zero.
24    ///
25    /// This error occurs when the input value for the reciprocal computation is zero,
26    /// as division by zero is undefined.
27    #[error("division by zero!")]
28    DivisionByZero {
29        /// A captured backtrace for debugging purposes.
30        backtrace: Backtrace,
31    },
32
33    /// The input value failed basic validation checks.
34    ///
35    /// This error occurs if the input value itself is considered invalid
36    /// according to the validation policy (e.g., [`StrictFinitePolicy`]),
37    /// such as being NaN or Infinity, before the reciprocal calculation
38    /// is attempted.
39    #[error("the input value is invalid according to validation policy")]
40    InvalidArgument {
41        /// The underlying validation error from the input type.
42        ///
43        /// This provides more specific details about why the input value
44        /// was considered invalid.
45        #[source]
46        #[backtrace]
47        source: <RawScalar as RawScalarTrait>::ValidationErrors,
48    },
49}
50
51/// A type alias for [`FunctionErrors`], specialized for errors that can occur during
52/// the computation of the reciprocal of a scalar number.
53///
54/// This type represents the possible failures when calling [`Reciprocal::try_reciprocal()`].
55/// It is generic over `RawScalar: RawScalarTrait`. This type alias wraps [`FunctionErrors`],
56/// where the input error source is [`ReciprocalInputErrors<RawScalar>`] and the output
57/// error source is `<RawScalar as RawScalarTrait>::ValidationErrors`.
58///
59/// # Variants
60///
61/// This type alias wraps [`FunctionErrors`], which has the following variants in this context:
62///
63/// - `Input { source: ReciprocalInputErrors<RawScalar> }`: Indicates that the input number
64///   was invalid for reciprocal computation. This could be due to failing initial validation
65///   (e.g., containing NaN or Infinity) or because the input was zero (division by zero).
66///   The `source` field provides more specific details via [`ReciprocalInputErrors`].
67///
68/// - `Output { source: <RawScalar as RawScalarTrait>::ValidationErrors }`: Indicates that the computed
69///   reciprocal value itself failed validation. This typically means the result of the
70///   reciprocal operation yielded a non-finite value (NaN or Infinity). The `source` field
71///   provides details, usually an instance of [`crate::validation::ErrorsValidationRawReal`]
72///   or [`crate::validation::ErrorsValidationRawComplex`].
73pub type ReciprocalErrors<RawScalar> = FunctionErrors<
74    ReciprocalInputErrors<RawScalar>,
75    <RawScalar as RawScalarTrait>::ValidationErrors,
76>;
77
78/// A trait for computing the reciprocal (`1/x`) of a number.
79///
80/// This trait provides an interface for calculating the reciprocal of a number,
81/// which can be real or complex. It includes both a fallible version (`try_reciprocal`)
82/// that performs validation and an infallible version (`reciprocal`) that may panic
83/// in debug builds if validation fails.
84///
85/// # Implementors
86///
87/// This trait is implemented for:
88/// - `f64`
89/// - [`Complex<f64>`](num::Complex)
90/// - `RealRugStrictFinite<PRECISION>` (when the `rug` feature is enabled)
91/// - `ComplexRugStrictFinite<PRECISION>` (when the `rug` feature is enabled)
92///
93/// # Validation Policy
94///
95/// Implementations typically use a [`StrictFinitePolicy`] for validating inputs and outputs.
96/// This means that NaN, Infinity, and subnormal numbers (for `f64`-based types)
97/// will generally result in an error or panic. Division by zero is also explicitly handled.
98pub trait Reciprocal: Sized {
99    /// The error type that can be returned by the `try_reciprocal` method.
100    ///
101    /// This is typically an instantiation of [`ReciprocalErrors`].
102    type Error: std::error::Error;
103
104    /// Attempts to compute the reciprocal of `self` (`1/self`), returning a `Result`.
105    ///
106    /// This method first validates the input `self` using [`StrictFinitePolicy`] and
107    /// also checks if it is zero. If the input is valid (finite, non-zero, normal),
108    /// it computes the reciprocal and then validates the result using the same policy.
109    fn try_reciprocal(self) -> Result<Self, <Self as Reciprocal>::Error>;
110
111    /// Returns the reciprocal of `self` (`1/self`).
112    fn reciprocal(self) -> Self;
113}
114
115#[duplicate_item(
116    T implementation trait_comment;
117    [f64] [recip()] ["Implementation of the [`Reciprocal`] trait for [`f64`]."];
118    [Complex::<f64>] [inv()] ["Implementation of the [`Reciprocal`] trait for [`Complex<f64>`]."];
119)]
120impl Reciprocal for T {
121    type Error = ReciprocalErrors<Self>;
122
123    /// Attempts to compute the reciprocal of `self` (`1/self`), returning a `Result`.
124    ///
125    /// This method first validates the input `self` using [`StrictFinitePolicy`] and
126    /// also checks if it is zero. If the input is valid (finite, non-zero, normal),
127    /// it computes the reciprocal and then validates the result using the same policy.
128    ///
129    /// # Returns
130    ///
131    /// - `Ok(Self)`: If both the input and the computed reciprocal are valid.
132    /// - `Err(Self::Error)`: If the input is invalid (e.g., NaN, Infinity, zero, subnormal)
133    ///   or if the computed reciprocal is invalid (e.g., NaN, Infinity, overflow).
134    ///
135    /// # Examples
136    ///
137    /// ```rust
138    /// use num_valid::{ComplexScalar, functions::{ComplexScalarConstructors, Reciprocal}};
139    /// use num::Complex;
140    /// use try_create::TryNew;
141    ///
142    /// // For f64
143    /// let x = 4.0_f64;
144    /// match x.try_reciprocal() {
145    ///     Ok(val) => println!("1/{} = {}", x, val), // 1/4.0 = 0.25
146    ///     Err(e) => println!("Error: {:?}", e),
147    /// }
148    ///
149    /// assert!(0.0_f64.try_reciprocal().is_err());
150    /// assert!(f64::NAN.try_reciprocal().is_err());
151    ///
152    /// // For Complex<f64>
153    /// let z = Complex::new(2.0, 0.0);
154    /// assert_eq!(z.try_reciprocal().unwrap(), Complex::try_new_pure_real(0.5).unwrap());
155    /// ```
156    #[inline(always)]
157    fn try_reciprocal(self) -> Result<Self, <Self as Reciprocal>::Error> {
158        StrictFinitePolicy::<T, 53>::validate(self)
159            .map_err(|e| ReciprocalInputErrors::InvalidArgument { source: e }.into())
160            .and_then(|value| {
161                if RawScalarTrait::is_zero(&value) {
162                    Err(ReciprocalInputErrors::DivisionByZero {
163                        backtrace: capture_backtrace(),
164                    }
165                    .into())
166                } else {
167                    // value is different from zero, so we can "safely" compute the reciprocal
168                    StrictFinitePolicy::<T, 53>::validate(value.implementation)
169                        .map_err(|e| ReciprocalErrors::<Self>::Output { source: e })
170                }
171            })
172    }
173
174    /// Returns the reciprocal of `self` (`1/self`).
175    ///
176    /// # Behavior
177    ///
178    /// - **Debug Builds (`#[cfg(debug_assertions)]`)**: This method internally calls `try_reciprocal().unwrap()`.
179    ///   It will panic if the input `self` is invalid (e.g., NaN, Infinity, zero, subnormal)
180    ///   or if the computed reciprocal is invalid.
181    /// - **Release Builds (`#[cfg(not(debug_assertions))]`)**: This method calls the underlying
182    ///   reciprocal function directly (e.g., `1.0 / x` for `f64`, `num::Complex::inv`).
183    ///   The behavior for non-finite inputs or division by zero (like NaN propagation or
184    ///   overflow resulting in Infinity) depends on the underlying implementation for the specific type.
185    ///
186    /// # Panics
187    ///
188    /// In debug builds, this method will panic if `try_reciprocal()` would return an `Err`.
189    ///
190    /// # Examples
191    ///
192    /// ```rust
193    /// use num_valid::functions::Reciprocal;
194    /// use num::Complex;
195    ///
196    /// let x = 2.0_f64;
197    /// println!("1/{} = {}", x, x.reciprocal()); // 1/2.0 = 0.5
198    ///
199    /// let z = Complex::new(0.0, -4.0); // 1 / (-4i) = i/4 = 0.25i
200    /// println!("1/({:?}) = {:?}", z, z.reciprocal()); // 1/Complex { re: 0.0, im: -4.0 } = Complex { re: 0.0, im: 0.25 }
201    /// ```
202    #[inline(always)]
203    fn reciprocal(self) -> Self {
204        #[cfg(debug_assertions)]
205        {
206            self.try_reciprocal().unwrap()
207        }
208        #[cfg(not(debug_assertions))]
209        {
210            self.implementation
211        }
212    }
213}
214
215//--------------------------------------------------------------------------------------------
216
217//------------------------------------------------------------------------------------------------
218#[cfg(test)]
219mod tests {
220    use super::*;
221    use crate::validation::{ErrorsValidationRawComplex, ErrorsValidationRawReal};
222    use num::Complex;
223
224    #[cfg(feature = "rug")]
225    use try_create::TryNew;
226
227    mod reciprocal {
228        use super::*;
229        mod native64 {
230            use super::*;
231
232            mod real {
233                use super::*;
234
235                #[test]
236                fn test_f64_reciprocal_valid() {
237                    let value = 4.0;
238                    assert_eq!(value.try_reciprocal().unwrap(), 0.25);
239                    assert_eq!(value.reciprocal(), 0.25);
240                }
241
242                #[test]
243                fn test_f64_reciprocal_zero() {
244                    let value = 0.0;
245                    let result = value.try_reciprocal();
246                    assert!(matches!(
247                        result,
248                        Err(ReciprocalErrors::<f64>::Input {
249                            source: ReciprocalInputErrors::DivisionByZero { .. }
250                        })
251                    ));
252                }
253
254                #[test]
255                fn test_f64_reciprocal_nan() {
256                    let value = f64::NAN;
257                    let result = value.try_reciprocal();
258                    assert!(matches!(
259                        result,
260                        Err(ReciprocalErrors::<f64>::Input {
261                            source: ReciprocalInputErrors::InvalidArgument {
262                                source: ErrorsValidationRawReal::IsNaN { .. }
263                            }
264                        })
265                    ));
266                }
267
268                #[test]
269                fn test_f64_reciprocal_subnormal() {
270                    let value = f64::MIN_POSITIVE / 2.0;
271                    let result = value.try_reciprocal();
272                    assert!(matches!(
273                        result,
274                        Err(ReciprocalErrors::<f64>::Input {
275                            source: ReciprocalInputErrors::InvalidArgument {
276                                source: ErrorsValidationRawReal::IsSubnormal { .. }
277                            }
278                        })
279                    ));
280                }
281
282                #[test]
283                fn test_f64_reciprocal_infinite() {
284                    let value = f64::INFINITY;
285                    let result = value.try_reciprocal();
286                    assert!(matches!(
287                        result,
288                        Err(ReciprocalErrors::<f64>::Input {
289                            source: ReciprocalInputErrors::InvalidArgument {
290                                source: ErrorsValidationRawReal::IsPosInfinity { .. }
291                            }
292                        })
293                    ));
294                }
295            }
296
297            mod complex {
298                use super::*;
299
300                #[test]
301                fn test_complex_f64_reciprocal_valid() {
302                    let value = Complex::new(4.0, 0.0);
303                    assert_eq!(value.try_reciprocal().unwrap(), Complex::new(0.25, 0.0));
304                    assert_eq!(value.reciprocal(), Complex::new(0.25, 0.0));
305                }
306
307                #[test]
308                fn test_complex_f64_reciprocal_zero() {
309                    let value = Complex::new(0.0, 0.0);
310                    let result = value.try_reciprocal();
311                    assert!(matches!(
312                        result,
313                        Err(ReciprocalErrors::<Complex<f64>>::Input {
314                            source: ReciprocalInputErrors::DivisionByZero { .. }
315                        })
316                    ));
317                }
318
319                #[test]
320                fn test_complex_f64_reciprocal_nan() {
321                    let value = Complex::new(f64::NAN, 0.0);
322                    assert!(matches!(
323                        value.try_reciprocal(),
324                        Err(ReciprocalErrors::<Complex<f64>>::Input {
325                            source: ReciprocalInputErrors::InvalidArgument {
326                                source: ErrorsValidationRawComplex::InvalidRealPart {
327                                    source: ErrorsValidationRawReal::IsNaN { .. }
328                                }
329                            }
330                        })
331                    ));
332
333                    let value = Complex::new(0.0, f64::NAN);
334                    assert!(matches!(
335                        value.try_reciprocal(),
336                        Err(ReciprocalErrors::<Complex<f64>>::Input {
337                            source: ReciprocalInputErrors::InvalidArgument {
338                                source: ErrorsValidationRawComplex::InvalidImaginaryPart {
339                                    source: ErrorsValidationRawReal::IsNaN { .. }
340                                }
341                            }
342                        })
343                    ));
344                }
345
346                #[test]
347                fn test_complex_f64_reciprocal_infinite() {
348                    let value = Complex::new(f64::INFINITY, 0.0);
349                    assert!(matches!(
350                        value.try_reciprocal(),
351                        Err(ReciprocalErrors::<Complex<f64>>::Input {
352                            source: ReciprocalInputErrors::InvalidArgument {
353                                source: ErrorsValidationRawComplex::InvalidRealPart {
354                                    source: ErrorsValidationRawReal::IsPosInfinity { .. }
355                                }
356                            }
357                        })
358                    ));
359
360                    let value = Complex::new(0.0, f64::INFINITY);
361                    assert!(matches!(
362                        value.try_reciprocal(),
363                        Err(ReciprocalErrors::<Complex<f64>>::Input {
364                            source: ReciprocalInputErrors::InvalidArgument {
365                                source: ErrorsValidationRawComplex::InvalidImaginaryPart {
366                                    source: ErrorsValidationRawReal::IsPosInfinity { .. }
367                                }
368                            }
369                        })
370                    ));
371                }
372
373                #[test]
374                fn test_complex_f64_reciprocal_sbnormal() {
375                    let value = Complex::new(f64::MIN_POSITIVE / 2.0, 0.0);
376                    assert!(matches!(
377                        value.try_reciprocal(),
378                        Err(ReciprocalErrors::<Complex<f64>>::Input {
379                            source: ReciprocalInputErrors::InvalidArgument {
380                                source: ErrorsValidationRawComplex::InvalidRealPart {
381                                    source: ErrorsValidationRawReal::IsSubnormal { .. }
382                                }
383                            }
384                        })
385                    ));
386
387                    let value = Complex::new(0.0, f64::MIN_POSITIVE / 2.0);
388                    assert!(matches!(
389                        value.try_reciprocal(),
390                        Err(ReciprocalErrors::<Complex<f64>>::Input {
391                            source: ReciprocalInputErrors::InvalidArgument {
392                                source: ErrorsValidationRawComplex::InvalidImaginaryPart {
393                                    source: ErrorsValidationRawReal::IsSubnormal { .. }
394                                }
395                            }
396                        })
397                    ));
398                }
399            }
400        }
401
402        #[cfg(feature = "rug")]
403        mod rug53 {
404            use super::*;
405            use crate::kernels::rug::{ComplexRugStrictFinite, RealRugStrictFinite};
406
407            mod real {
408                use super::*;
409
410                #[test]
411                fn test_rug_float_reciprocal_valid() {
412                    let value =
413                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 4.0)).unwrap();
414                    let result = value.try_reciprocal();
415                    assert!(result.is_ok());
416                }
417
418                #[test]
419                fn test_rug_float_reciprocal_zero() {
420                    let value =
421                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 0.0)).unwrap();
422                    let result = value.try_reciprocal();
423                    assert!(matches!(
424                        result,
425                        Err(ReciprocalErrors::Input {
426                            source: ReciprocalInputErrors::DivisionByZero { .. }
427                        })
428                    ));
429                }
430            }
431
432            mod complex {
433                use super::*;
434
435                #[test]
436                fn test_complex_rug_float_reciprocal_valid() {
437                    let value = ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
438                        53,
439                        (rug::Float::with_val(53, 4.0), rug::Float::with_val(53, 0.0)),
440                    ))
441                    .unwrap();
442
443                    let expected_result =
444                        ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
445                            53,
446                            (
447                                rug::Float::with_val(53, 0.25),
448                                rug::Float::with_val(53, 0.0),
449                            ),
450                        ))
451                        .unwrap();
452
453                    assert_eq!(value.clone().try_reciprocal().unwrap(), expected_result);
454                    assert_eq!(value.reciprocal(), expected_result);
455                }
456
457                #[test]
458                fn test_complex_rug_float_reciprocal_zero() {
459                    let value = ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
460                        53,
461                        (rug::Float::with_val(53, 0.0), rug::Float::with_val(53, 0.0)),
462                    ))
463                    .unwrap();
464                    assert!(matches!(
465                        value.try_reciprocal(),
466                        Err(ReciprocalErrors::Input {
467                            source: ReciprocalInputErrors::DivisionByZero { .. }
468                        })
469                    ));
470                }
471            }
472        }
473    }
474}
475//------------------------------------------------------------------------------------------------