Skip to main content

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    use std::assert_matches;
230
231    #[cfg(feature = "rug")]
232    use try_create::TryNew;
233
234    mod reciprocal {
235        use super::*;
236        mod native64 {
237            use super::*;
238
239            mod real {
240                use super::*;
241
242                #[test]
243                fn test_f64_reciprocal_valid() {
244                    let value = 4.0;
245                    assert_eq!(value.try_reciprocal().unwrap(), 0.25);
246                    assert_eq!(value.reciprocal(), 0.25);
247                }
248
249                #[test]
250                fn test_f64_reciprocal_zero() {
251                    let value = 0.0;
252                    let result = value.try_reciprocal();
253                    assert!(matches!(
254                        result,
255                        Err(ReciprocalErrors::<f64>::Input {
256                            source: ReciprocalInputErrors::DivisionByZero { .. }
257                        })
258                    ));
259                }
260
261                #[test]
262                fn test_f64_reciprocal_nan() {
263                    let value = f64::NAN;
264                    let result = value.try_reciprocal();
265                    assert!(matches!(
266                        result,
267                        Err(ReciprocalErrors::<f64>::Input {
268                            source: ReciprocalInputErrors::InvalidArgument {
269                                source: ErrorsValidationRawReal::IsNaN { .. }
270                            }
271                        })
272                    ));
273                }
274
275                #[test]
276                fn test_f64_reciprocal_subnormal() {
277                    let value = f64::MIN_POSITIVE / 2.0;
278                    let result = value.try_reciprocal();
279                    assert!(matches!(
280                        result,
281                        Err(ReciprocalErrors::<f64>::Input {
282                            source: ReciprocalInputErrors::InvalidArgument {
283                                source: ErrorsValidationRawReal::IsSubnormal { .. }
284                            }
285                        })
286                    ));
287                }
288
289                #[test]
290                fn test_f64_reciprocal_infinite() {
291                    let value = f64::INFINITY;
292                    let result = value.try_reciprocal();
293                    assert!(matches!(
294                        result,
295                        Err(ReciprocalErrors::<f64>::Input {
296                            source: ReciprocalInputErrors::InvalidArgument {
297                                source: ErrorsValidationRawReal::IsPosInfinity { .. }
298                            }
299                        })
300                    ));
301                }
302            }
303
304            mod complex {
305                use super::*;
306
307                #[test]
308                fn test_complex_f64_reciprocal_valid() {
309                    let value = Complex::new(4.0, 0.0);
310                    assert_eq!(value.try_reciprocal().unwrap(), Complex::new(0.25, 0.0));
311                    assert_eq!(value.reciprocal(), Complex::new(0.25, 0.0));
312                }
313
314                #[test]
315                fn test_complex_f64_reciprocal_zero() {
316                    let value = Complex::new(0.0, 0.0);
317                    let result = value.try_reciprocal();
318                    assert!(matches!(
319                        result,
320                        Err(ReciprocalErrors::<Complex<f64>>::Input {
321                            source: ReciprocalInputErrors::DivisionByZero { .. }
322                        })
323                    ));
324                }
325
326                #[test]
327                fn test_complex_f64_reciprocal_nan() {
328                    let value = Complex::new(f64::NAN, 0.0);
329                    let err = value.try_reciprocal().unwrap_err();
330                    match err {
331                        ReciprocalErrors::<Complex<f64>>::Input {
332                            source:
333                                ReciprocalInputErrors::InvalidArgument {
334                                    source: ErrorsValidationRawComplex::InvalidRealPart { source },
335                                },
336                        } => {
337                            assert_matches!(*source, ErrorsValidationRawReal::IsNaN { .. });
338                        }
339                        _ => {
340                            unreachable!();
341                        }
342                    }
343
344                    let value = Complex::new(0.0, f64::NAN);
345                    let err = value.try_reciprocal().unwrap_err();
346                    match err {
347                        ReciprocalErrors::<Complex<f64>>::Input {
348                            source:
349                                ReciprocalInputErrors::InvalidArgument {
350                                    source:
351                                        ErrorsValidationRawComplex::InvalidImaginaryPart { source },
352                                },
353                        } => {
354                            assert_matches!(*source, ErrorsValidationRawReal::IsNaN { .. });
355                        }
356                        _ => {
357                            unreachable!();
358                        }
359                    }
360                }
361
362                #[test]
363                fn test_complex_f64_reciprocal_infinite() {
364                    let value = Complex::new(f64::INFINITY, 0.0);
365                    let err = value.try_reciprocal().unwrap_err();
366                    match err {
367                        ReciprocalErrors::<Complex<f64>>::Input {
368                            source:
369                                ReciprocalInputErrors::InvalidArgument {
370                                    source: ErrorsValidationRawComplex::InvalidRealPart { source },
371                                },
372                        } => {
373                            assert_matches!(*source, ErrorsValidationRawReal::IsPosInfinity { .. });
374                        }
375                        _ => {
376                            unreachable!();
377                        }
378                    }
379
380                    let value = Complex::new(0.0, f64::INFINITY);
381                    let err = value.try_reciprocal().unwrap_err();
382                    match err {
383                        ReciprocalErrors::<Complex<f64>>::Input {
384                            source:
385                                ReciprocalInputErrors::InvalidArgument {
386                                    source:
387                                        ErrorsValidationRawComplex::InvalidImaginaryPart { source },
388                                },
389                        } => {
390                            assert_matches!(*source, ErrorsValidationRawReal::IsPosInfinity { .. });
391                        }
392                        _ => {
393                            unreachable!();
394                        }
395                    }
396                }
397
398                #[test]
399                fn test_complex_f64_reciprocal_sbnormal() {
400                    let value = Complex::new(f64::MIN_POSITIVE / 2.0, 0.0);
401                    let err = value.try_reciprocal().unwrap_err();
402                    match err {
403                        ReciprocalErrors::<Complex<f64>>::Input {
404                            source:
405                                ReciprocalInputErrors::InvalidArgument {
406                                    source: ErrorsValidationRawComplex::InvalidRealPart { source },
407                                },
408                        } => {
409                            assert_matches!(*source, ErrorsValidationRawReal::IsSubnormal { .. });
410                        }
411                        _ => {
412                            unreachable!();
413                        }
414                    }
415
416                    let value = Complex::new(0.0, f64::MIN_POSITIVE / 2.0);
417                    let err = value.try_reciprocal().unwrap_err();
418                    match err {
419                        ReciprocalErrors::<Complex<f64>>::Input {
420                            source:
421                                ReciprocalInputErrors::InvalidArgument {
422                                    source:
423                                        ErrorsValidationRawComplex::InvalidImaginaryPart { source },
424                                },
425                        } => {
426                            assert_matches!(*source, ErrorsValidationRawReal::IsSubnormal { .. });
427                        }
428                        _ => {
429                            unreachable!();
430                        }
431                    }
432                }
433            }
434
435            #[cfg(feature = "rug")]
436            mod rug53 {
437                use super::*;
438                use crate::backends::rug::validated::{
439                    ComplexRugStrictFinite, RealRugStrictFinite,
440                };
441
442                mod real {
443                    use super::*;
444
445                    #[test]
446                    fn test_rug_float_reciprocal_valid() {
447                        let value =
448                            RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 4.0))
449                                .unwrap();
450                        let result = value.try_reciprocal();
451                        assert!(result.is_ok());
452                    }
453
454                    #[test]
455                    fn test_rug_float_reciprocal_zero() {
456                        let value =
457                            RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 0.0))
458                                .unwrap();
459                        let result = value.try_reciprocal();
460                        assert!(matches!(
461                            result,
462                            Err(ReciprocalErrors::Input {
463                                source: ReciprocalInputErrors::DivisionByZero { .. }
464                            })
465                        ));
466                    }
467                }
468
469                mod complex {
470                    use super::*;
471
472                    #[test]
473                    fn test_complex_rug_float_reciprocal_valid() {
474                        let value = ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
475                            53,
476                            (rug::Float::with_val(53, 4.0), rug::Float::with_val(53, 0.0)),
477                        ))
478                        .unwrap();
479
480                        let expected_result =
481                            ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
482                                53,
483                                (
484                                    rug::Float::with_val(53, 0.25),
485                                    rug::Float::with_val(53, 0.0),
486                                ),
487                            ))
488                            .unwrap();
489
490                        assert_eq!(value.clone().try_reciprocal().unwrap(), expected_result);
491                        assert_eq!(value.reciprocal(), expected_result);
492                    }
493
494                    #[test]
495                    fn test_complex_rug_float_reciprocal_zero() {
496                        let value = ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
497                            53,
498                            (rug::Float::with_val(53, 0.0), rug::Float::with_val(53, 0.0)),
499                        ))
500                        .unwrap();
501                        assert!(matches!(
502                            value.try_reciprocal(),
503                            Err(ReciprocalErrors::Input {
504                                source: ReciprocalInputErrors::DivisionByZero { .. }
505                            })
506                        ));
507                    }
508                }
509            }
510        }
511    }
512}
513//------------------------------------------------------------------------------------------------