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: ReciprocalInputErrors::InvalidArgument {
332                                source: ErrorsValidationRawComplex::InvalidRealPart {
333                                    source: ErrorsValidationRawReal::IsNaN { .. }
334                                }
335                            }
336                        })
337                    ));
338
339                    let value = Complex::new(0.0, f64::NAN);
340                    assert!(matches!(
341                        value.try_reciprocal(),
342                        Err(ReciprocalErrors::<Complex<f64>>::Input {
343                            source: ReciprocalInputErrors::InvalidArgument {
344                                source: ErrorsValidationRawComplex::InvalidImaginaryPart {
345                                    source: ErrorsValidationRawReal::IsNaN { .. }
346                                }
347                            }
348                        })
349                    ));
350                }
351
352                #[test]
353                fn test_complex_f64_reciprocal_infinite() {
354                    let value = Complex::new(f64::INFINITY, 0.0);
355                    assert!(matches!(
356                        value.try_reciprocal(),
357                        Err(ReciprocalErrors::<Complex<f64>>::Input {
358                            source: ReciprocalInputErrors::InvalidArgument {
359                                source: ErrorsValidationRawComplex::InvalidRealPart {
360                                    source: ErrorsValidationRawReal::IsPosInfinity { .. }
361                                }
362                            }
363                        })
364                    ));
365
366                    let value = Complex::new(0.0, f64::INFINITY);
367                    assert!(matches!(
368                        value.try_reciprocal(),
369                        Err(ReciprocalErrors::<Complex<f64>>::Input {
370                            source: ReciprocalInputErrors::InvalidArgument {
371                                source: ErrorsValidationRawComplex::InvalidImaginaryPart {
372                                    source: ErrorsValidationRawReal::IsPosInfinity { .. }
373                                }
374                            }
375                        })
376                    ));
377                }
378
379                #[test]
380                fn test_complex_f64_reciprocal_sbnormal() {
381                    let value = Complex::new(f64::MIN_POSITIVE / 2.0, 0.0);
382                    assert!(matches!(
383                        value.try_reciprocal(),
384                        Err(ReciprocalErrors::<Complex<f64>>::Input {
385                            source: ReciprocalInputErrors::InvalidArgument {
386                                source: ErrorsValidationRawComplex::InvalidRealPart {
387                                    source: ErrorsValidationRawReal::IsSubnormal { .. }
388                                }
389                            }
390                        })
391                    ));
392
393                    let value = Complex::new(0.0, f64::MIN_POSITIVE / 2.0);
394                    assert!(matches!(
395                        value.try_reciprocal(),
396                        Err(ReciprocalErrors::<Complex<f64>>::Input {
397                            source: ReciprocalInputErrors::InvalidArgument {
398                                source: ErrorsValidationRawComplex::InvalidImaginaryPart {
399                                    source: ErrorsValidationRawReal::IsSubnormal { .. }
400                                }
401                            }
402                        })
403                    ));
404                }
405            }
406        }
407
408        #[cfg(feature = "rug")]
409        mod rug53 {
410            use super::*;
411            use crate::backends::rug::validated::{ComplexRugStrictFinite, RealRugStrictFinite};
412
413            mod real {
414                use super::*;
415
416                #[test]
417                fn test_rug_float_reciprocal_valid() {
418                    let value =
419                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 4.0)).unwrap();
420                    let result = value.try_reciprocal();
421                    assert!(result.is_ok());
422                }
423
424                #[test]
425                fn test_rug_float_reciprocal_zero() {
426                    let value =
427                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 0.0)).unwrap();
428                    let result = value.try_reciprocal();
429                    assert!(matches!(
430                        result,
431                        Err(ReciprocalErrors::Input {
432                            source: ReciprocalInputErrors::DivisionByZero { .. }
433                        })
434                    ));
435                }
436            }
437
438            mod complex {
439                use super::*;
440
441                #[test]
442                fn test_complex_rug_float_reciprocal_valid() {
443                    let value = ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
444                        53,
445                        (rug::Float::with_val(53, 4.0), rug::Float::with_val(53, 0.0)),
446                    ))
447                    .unwrap();
448
449                    let expected_result =
450                        ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
451                            53,
452                            (
453                                rug::Float::with_val(53, 0.25),
454                                rug::Float::with_val(53, 0.0),
455                            ),
456                        ))
457                        .unwrap();
458
459                    assert_eq!(value.clone().try_reciprocal().unwrap(), expected_result);
460                    assert_eq!(value.reciprocal(), expected_result);
461                }
462
463                #[test]
464                fn test_complex_rug_float_reciprocal_zero() {
465                    let value = ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
466                        53,
467                        (rug::Float::with_val(53, 0.0), rug::Float::with_val(53, 0.0)),
468                    ))
469                    .unwrap();
470                    assert!(matches!(
471                        value.try_reciprocal(),
472                        Err(ReciprocalErrors::Input {
473                            source: ReciprocalInputErrors::DivisionByZero { .. }
474                        })
475                    ));
476                }
477            }
478        }
479    }
480}
481//------------------------------------------------------------------------------------------------