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