num_valid/functions/
reciprocal.rs

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