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