num_valid/functions/
reciprocal.rs

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