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