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