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