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