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//------------------------------------------------------------------------------------------------