Skip to main content

num_valid/functions/
sqrt.rs

1#![deny(rustdoc::broken_intra_doc_links)]
2
3//! Square root operations.
4//!
5//! This module provides the [`Sqrt`] trait for computing square roots of validated
6//! real and complex numbers.
7
8use crate::{
9    core::traits::{raw::RawScalarValidate, validation::sealed::NativeReal},
10    functions::FunctionErrors,
11    kernels::{RawComplexTrait, RawRealTrait},
12};
13use num::Complex;
14use num_traits::Float;
15use std::{backtrace::Backtrace, fmt};
16use thiserror::Error;
17
18#[cfg(debug_assertions)]
19use crate::core::errors::capture_backtrace;
20
21//------------------------------------------------------------------------------------------------
22/// Errors that can occur during the input validation phase when attempting to compute
23/// the square root of a real number.
24///
25/// This enum is used as the source for the [`Input`](SqrtRealErrors::Input) variant of [`SqrtRealErrors`].
26/// It is generic over `RawReal: RawRealTrait`, which defines the specific raw real number type and its associated
27/// validation error type.
28///
29/// # Variants
30///
31/// - [`Self::NegativeValue`]: The input value is negative, which is not allowed for real square roots.
32/// - [`Self::ValidationError`]: The input value failed basic validation checks (e.g., NaN, infinity, subnormal) according to the validation policy.
33#[derive(Debug, Error)]
34pub enum SqrtRealInputErrors<RawReal: RawRealTrait> {
35    /// The input value for the square root computation is negative.
36    ///
37    /// The square root of a negative real number is not a real number.
38    #[error("the input value ({value:?}) is negative!")]
39    NegativeValue {
40        /// The negative input value.
41        value: RawReal,
42
43        /// The backtrace of the error.
44        backtrace: Backtrace,
45    },
46
47    /// The input value failed basic validation checks (e.g., it is NaN, infinite, or subnormal).
48    ///
49    /// This error occurs if the input value itself is considered invalid
50    /// according to the validation policy (e.g., [`StrictFinitePolicy`](crate::core::policies::StrictFinitePolicy)),
51    /// before the domain-specific check (like negativity) for the square root
52    /// operation is performed.
53    #[error("the input value is invalid!")]
54    ValidationError {
55        /// The source error that occurred during validation.
56        #[source]
57        #[backtrace]
58        source: <RawReal as RawScalarValidate>::ValidationError,
59    },
60}
61
62/// Errors that can occur during the input validation phase when computing the square root of a complex number.
63///
64/// This enum is used as the source for the [`Input`](SqrtComplexErrors::Input) variant of [`SqrtComplexErrors`].
65/// It is generic over `RawComplex: RawComplexTrait`.
66///
67/// # Variants
68///
69/// - [`Self::ValidationError`]: The input complex number failed basic validation checks (e.g., its components are NaN, infinite, or subnormal) according to the validation policy.
70#[derive(Debug, Error)]
71pub enum SqrtComplexInputErrors<RawComplex: RawComplexTrait> {
72    /// The input complex number failed basic validation checks (e.g., its components are NaN, infinite, or subnormal).
73    ///
74    /// This error occurs if the input complex value itself is considered invalid
75    /// according to the validation policy (e.g., [`StrictFinitePolicy`](crate::core::policies::StrictFinitePolicy)),
76    /// before any domain-specific checks for the complex square root operation are performed.
77    /// It signifies that the real or imaginary part (or both) does not meet fundamental validity criteria.
78    #[error("the input value is invalid!")]
79    ValidationError {
80        /// The detailed source error from the raw complex number's validation.
81        ///
82        /// This field encapsulates the specific error of type `<RawComplex as RawScalarValidate>::ValidationError`
83        /// that was reported during the validation of the input complex number's components.
84        #[source]
85        #[backtrace]
86        source: <RawComplex as RawScalarValidate>::ValidationError,
87    },
88}
89
90/// A type alias for [`FunctionErrors`], specialized for errors that can occur during
91/// the computation of the square root of a real number.
92///
93/// This type represents the possible failures when calling [`Sqrt::try_sqrt()`] on a real number.
94///
95/// # Generic Parameters
96///
97/// - `RawReal`: A type that implements [`RawRealTrait`]. This defines:
98///   - The raw error type for the input real number via `SqrtRealInputErrors<RawReal>`.
99///   - The raw error type for the output real number (the square root) also via `<RawReal as RawScalarValidate>::ValidationError`.
100///
101/// # Variants
102///
103/// This type alias wraps [`FunctionErrors`], which has the following variants in this context:
104///
105/// - `Input { source: SqrtRealInputErrors<RawReal> }`:
106///   Indicates that the input real number was invalid for square root computation.
107///   This could be due to the number being negative or failing general validation checks
108///   (e.g., NaN, Infinity, subnormal). The `source` field provides more specific details
109///   via [`SqrtRealInputErrors`].
110///
111/// - `Output { source: <RawReal as RawScalarValidate>::ValidationError }`:
112///   Indicates that the computed square root (which should be a real number)
113///   failed validation. This typically means the result of the `sqrt` operation yielded
114///   a non-finite value (NaN or Infinity), which is unexpected if the input was valid
115///   (non-negative and finite).
116pub type SqrtRealErrors<RawReal> =
117    FunctionErrors<SqrtRealInputErrors<RawReal>, <RawReal as RawScalarValidate>::ValidationError>;
118
119/// A type alias for [`FunctionErrors`], specialized for errors that can occur during
120/// the computation of the square root of a complex number.
121///
122/// This type represents the possible failures when calling [`Sqrt::try_sqrt()`] on a complex number.
123///
124/// # Generic Parameters
125///
126/// - `RawComplex`: A type that implements [`RawComplexTrait`]. This defines:
127///   - The raw error type for the input complex number via `SqrtComplexInputErrors<RawComplex>`.
128///   - The raw error type for the output complex number (the square root) also via `<RawComplex as RawScalarValidate>::ValidationError`.
129///
130/// # Variants
131///
132/// This type alias wraps [`FunctionErrors`], which has the following variants in this context:
133///
134/// - `Input { source: SqrtComplexInputErrors<RawComplex> }`:
135///   Indicates that the input complex number was invalid for square root computation.
136///   This is typically due to the complex number's components (real or imaginary parts)
137///   failing general validation checks (e.g., NaN, Infinity, subnormal).
138///   The `source` field provides more specific details via [`SqrtComplexInputErrors`].
139///
140/// - `Output { source: <RawComplex as RawScalarValidate>::ValidationError }`:
141///   Indicates that the computed complex square root itself failed validation.
142///   This typically means the result of the `sqrt` operation yielded a complex number
143///   with non-finite components (NaN or Infinity), which is unexpected if the input was valid.
144pub type SqrtComplexErrors<RawComplex> = FunctionErrors<
145    SqrtComplexInputErrors<RawComplex>,
146    <RawComplex as RawScalarValidate>::ValidationError,
147>;
148//--------------------------------------------------------------------------------------------
149
150//--------------------------------------------------------------------------------------------
151/// A trait for computing the principal square root of a number.
152///
153/// The principal square root of a non-negative real number `x` is the unique non-negative real number `y`
154/// such that `y^2 = x`.
155/// For a complex number `z`, its square root `w` satisfies `w^2 = z`. Complex numbers
156/// (except 0) have two square roots; this trait computes the principal square root,
157/// typically defined as `exp(0.5 * log(z))`, which usually has a non-negative real part.
158///
159/// This trait provides both a fallible version ([`try_sqrt`](Sqrt::try_sqrt)) that performs validation in debug builds
160/// and an infallible version ([`sqrt`](Sqrt::sqrt)) that may panic in debug builds if validation fails.
161pub trait Sqrt: Sized {
162    /// The error type that can be returned by the `try_sqrt` method.
163    type Error: fmt::Debug;
164
165    /// Attempts to compute the principal square root of `self`, returning a `Result`.
166    ///
167    /// Implementations should validate the input `self` according to the domain
168    /// (e.g., non-negative for reals) and a general validation policy (e.g., [`StrictFinitePolicy`](crate::core::policies::StrictFinitePolicy)).
169    /// If the input is valid, the square root is computed, and then the result
170    /// is also validated using the same policy.
171    ///
172    /// # Returns
173    ///
174    /// - `Ok(Self)`: If the input is valid for the square root operation and both the input
175    ///   and the computed square root satisfy the validation policy.
176    /// - `Err(SqrtRealErrors)`: If the input is invalid (e.g., negative for real sqrt, NaN, Infinity)
177    ///   or if the computed square root is invalid (see below).
178    ///
179    /// # Errors
180    ///
181    /// - Returns [`SqrtRealErrors::Input`] (for reals) or [`SqrtComplexErrors::Input`] (for complex)
182    ///   via [`FunctionErrors::Input`] if the input is invalid (e.g., negative real, NaN, Infinity, subnormal).
183    /// - Returns [`SqrtRealErrors::Output`] or [`SqrtComplexErrors::Output`] via [`FunctionErrors::Output`]
184    ///   if the result of the computation is not finite (e.g., NaN, Infinity) as per the validation policy.
185    ///
186    /// # Examples
187    ///
188    /// ```no_run
189    /// use num_valid::{*, functions::{Abs, Sqrt}};
190    /// use num::Complex;
191    ///
192    /// assert_eq!(4.0_f64.try_sqrt().unwrap(), 2.0);
193    /// #[cfg(debug_assertions)]
194    /// {
195    ///     assert!((-1.0_f64).try_sqrt().is_err()); // Negative real
196    ///     assert!(f64::NAN.try_sqrt().is_err());
197    /// }
198    ///
199    /// let z = Complex::new(-4.0, 0.0); // sqrt(-4) = 2i
200    /// let sqrt_z = z.try_sqrt().unwrap();
201    ///
202    /// let tol = 1e-9;
203    /// assert!(<f64 as Abs>::abs(sqrt_z.re).as_raw_scalar_ref() < &tol &&
204    ///     <f64 as Abs>::abs(sqrt_z.im - 2.0).as_raw_scalar_ref() < &tol);
205    /// ```
206    #[must_use = "this `Result` may contain an error that should be handled"]
207    fn try_sqrt(self) -> Result<Self, <Self as Sqrt>::Error>;
208
209    /// Computes the square principal square root of `self`.
210    fn sqrt(self) -> Self;
211}
212
213impl<T> Sqrt for T
214where
215    T: NativeReal,
216{
217    type Error = SqrtRealErrors<T>;
218
219    /// Attempts to compute the principal square root of `self`, returning a `Result`.
220    ///
221    /// Implementations should validate the input `self` according to the domain
222    /// (e.g., non-negative for reals) and a general validation policy (e.g., [`StrictFinitePolicy`](crate::core::policies::StrictFinitePolicy)).
223    /// If the input is valid, the square root is computed, and then the result
224    /// is also validated using the same policy.
225    ///
226    /// # Returns
227    ///
228    /// - `Ok(Self)`: If the input is valid for the square root operation and both the input
229    ///   and the computed square root satisfy the validation policy.
230    /// - `Err(Self::Error)`: If the input is invalid (e.g., negative for real sqrt, NaN, Infinity)
231    ///   or if the computed square root is invalid.
232    ///
233    /// # Examples
234    ///
235    /// ```no_run
236    /// use num_valid::{*, functions::{Abs, Sqrt}};
237    /// use num::Complex;
238    ///
239    /// assert_eq!(4.0_f64.try_sqrt().unwrap(), 2.0);
240    /// #[cfg(debug_assertions)]
241    /// {
242    ///     assert!((-1.0_f64).try_sqrt().is_err()); // Negative real
243    ///     assert!(f64::NAN.try_sqrt().is_err());
244    /// }
245    ///
246    /// let z = Complex::new(-4.0, 0.0); // sqrt(-4) = 2i
247    /// let sqrt_z = z.try_sqrt().unwrap();
248    ///
249    /// let tol = 1e-9;
250    /// assert!(<f64 as Abs>::abs(sqrt_z.re).as_raw_scalar_ref() < &tol &&
251    ///     <f64 as Abs>::abs(sqrt_z.im - 2.0).as_raw_scalar_ref() < &tol);
252    /// ```
253    #[inline(always)]
254    fn try_sqrt(self) -> Result<T, SqrtRealErrors<T>> {
255        #[cfg(debug_assertions)]
256        {
257            self.validate_raw_scalar()
258                .map_err(|e| SqrtRealInputErrors::ValidationError { source: e }.into())
259                .and_then(|validated_value| {
260                    if validated_value < T::zero() {
261                        Err(SqrtRealInputErrors::NegativeValue {
262                            value: validated_value,
263                            backtrace: capture_backtrace(),
264                        }
265                        .into())
266                    } else {
267                        <T as Float>::sqrt(validated_value)
268                            .validate_raw_scalar()
269                            .map_err(|e| SqrtRealErrors::Output { source: e })
270                    }
271                })
272        }
273        #[cfg(not(debug_assertions))]
274        {
275            Ok(<T as Float>::sqrt(self))
276        }
277    }
278
279    /// Computes and returns the principal square root of `self`.
280    ///
281    /// # Behavior
282    ///
283    /// - **Debug Builds (`#[cfg(debug_assertions)]`)**: This method internally calls
284    ///   [`try_sqrt().unwrap()`](Sqrt::try_sqrt). It will panic if `try_sqrt` returns an `Err`.
285    /// - **Release Builds (`#[cfg(not(debug_assertions))]`)**: This method calls the underlying
286    ///   square root function directly (e.g., `f64::sqrt`).
287    ///   The behavior for invalid inputs (like `sqrt(-1.0)` for `f64` returning NaN)
288    ///   depends on the underlying implementation.
289    ///
290    /// # Panics
291    ///
292    /// In debug builds, this method will panic if [`try_sqrt()`](Sqrt::try_sqrt) would return an `Err`.
293    ///
294    /// # Examples
295    ///
296    /// ```
297    /// use num_valid::functions::Sqrt;
298    /// use num::Complex;
299    ///
300    /// assert_eq!(9.0_f64.sqrt(), 3.0);
301    ///
302    /// // For f64, sqrt of negative is NaN in release, panics in debug with ftl's Sqrt
303    /// #[cfg(not(debug_assertions))]
304    /// assert!((-1.0_f64).sqrt().is_nan());
305    ///
306    /// let z = Complex::new(0.0, 4.0); // sqrt(4i) = sqrt(2) + i*sqrt(2)
307    /// let sqrt_z = z.sqrt();
308    /// let expected_val = std::f64::consts::SQRT_2;
309    /// assert!((sqrt_z.re - expected_val).abs() < 1e-9 && (sqrt_z.im - expected_val).abs() < 1e-9);
310    /// ```
311    #[inline(always)]
312    fn sqrt(self) -> Self {
313        #[cfg(debug_assertions)]
314        {
315            self.try_sqrt().unwrap()
316        }
317        #[cfg(not(debug_assertions))]
318        {
319            <T as Float>::sqrt(self)
320        }
321    }
322}
323
324impl<T> Sqrt for Complex<T>
325where
326    Self: RawComplexTrait<RawReal = T>,
327    T: NativeReal,
328{
329    type Error = SqrtComplexErrors<Complex<T>>;
330
331    /// Attempts to compute the principal square root of `self` (a `Complex<f64>`).
332    ///
333    /// This method first validates `self` using [`StrictFinitePolicy`](crate::core::policies::StrictFinitePolicy) (components must be finite and normal).
334    /// If valid, it computes `Complex::sqrt` and validates the result using [`StrictFinitePolicy`](crate::core::policies::StrictFinitePolicy).
335    ///
336    /// # Returns
337    ///
338    /// - `Ok(Complex<f64>)`: If `self` and the computed square root have finite and normal components.
339    /// - `Err(SqrtComplexErrors<Complex<f64>>)`: If `self` or the result has invalid components.
340    #[inline(always)]
341    fn try_sqrt(self) -> Result<Self, <Self as Sqrt>::Error> {
342        #[cfg(debug_assertions)]
343        {
344            self.validate_raw_scalar()
345                .map_err(|e| SqrtComplexInputErrors::ValidationError { source: e }.into())
346                .and_then(|validated_value| {
347                    Complex::<T>::sqrt(validated_value)
348                        .validate_raw_scalar()
349                        .map_err(|e| SqrtComplexErrors::Output { source: e })
350                })
351        }
352        #[cfg(not(debug_assertions))]
353        {
354            Ok(Complex::<T>::sqrt(self))
355        }
356    }
357
358    /// Computes and returns the principal square root of `self` (a `Complex<f64>`).
359    ///
360    /// # Behavior
361    ///
362    /// - **Debug Builds**: Calls `try_sqrt().unwrap()`. Panics on invalid input/output.
363    /// - **Release Builds**: Calls `Complex::sqrt(self)` directly.
364    ///
365    /// # Panics
366    ///
367    /// In debug builds, if `try_sqrt()` would return an `Err`.
368    #[inline(always)]
369    fn sqrt(self) -> Self {
370        #[cfg(debug_assertions)]
371        {
372            self.try_sqrt().unwrap()
373        }
374        #[cfg(not(debug_assertions))]
375        {
376            Complex::<T>::sqrt(self)
377        }
378    }
379}
380
381//------------------------------------------------------------------------------------------------
382
383//------------------------------------------------------------------------------------------------
384#[cfg(test)]
385mod tests {
386    use super::*;
387    use num::Complex;
388
389    #[cfg(feature = "rug")]
390    use try_create::TryNew;
391
392    mod sqrt {
393        use super::*;
394
395        mod native {
396            use super::*;
397
398            mod real {
399                use super::*;
400
401                #[test]
402                fn test_f64_sqrt_valid() {
403                    let value = 4.0;
404
405                    assert_eq!(value.try_sqrt().unwrap(), 2.0);
406                    assert_eq!(<f64 as Sqrt>::sqrt(value), 2.0);
407                }
408
409                #[test]
410                #[cfg(debug_assertions)]
411                fn test_f64_sqrt_negative_value() {
412                    let value = -4.0;
413                    let result = value.try_sqrt();
414                    assert!(matches!(result, Err(SqrtRealErrors::Input(..))));
415                }
416
417                #[test]
418                #[cfg(debug_assertions)]
419                fn test_f64_sqrt_subnormal() {
420                    let value = f64::MIN_POSITIVE / 2.0;
421                    let result = value.try_sqrt();
422                    assert!(matches!(result, Err(SqrtRealErrors::Input(..))));
423                }
424
425                #[test]
426                fn test_f64_sqrt_zero() {
427                    let value = 0.0;
428                    let result = value.try_sqrt();
429                    assert!(matches!(result, Ok(0.0)));
430                }
431
432                #[test]
433                #[cfg(debug_assertions)]
434                fn test_f64_sqrt_nan() {
435                    let value = f64::NAN;
436                    let result = value.try_sqrt();
437                    assert!(matches!(result, Err(SqrtRealErrors::Input(..))));
438                }
439
440                #[test]
441                #[cfg(debug_assertions)]
442                fn test_f64_sqrt_infinite() {
443                    let value = f64::INFINITY;
444                    let result = value.try_sqrt();
445                    println!("result: {result:?}");
446                    assert!(matches!(result, Err(SqrtRealErrors::Input(..))));
447                }
448            }
449
450            mod complex {
451                use super::*;
452
453                #[test]
454                fn test_complex_f64_sqrt_valid() {
455                    let value = Complex::new(4.0, 0.0);
456
457                    let expected_result = Complex::new(2.0, 0.0);
458
459                    assert_eq!(value.try_sqrt().unwrap(), expected_result);
460                    assert_eq!(<Complex<f64> as Sqrt>::sqrt(value), expected_result);
461                }
462
463                #[test]
464                #[cfg(debug_assertions)]
465                fn test_complex_f64_sqrt_invalid() {
466                    let value = Complex::new(f64::NAN, 0.0);
467                    let result = value.try_sqrt();
468                    assert!(matches!(result, Err(SqrtComplexErrors::Input(..))));
469
470                    let value = Complex::new(0.0, f64::NAN);
471                    let result = value.try_sqrt();
472                    assert!(matches!(result, Err(SqrtComplexErrors::Input(..))));
473
474                    let value = Complex::new(f64::INFINITY, 0.0);
475                    let result = value.try_sqrt();
476                    assert!(matches!(result, Err(SqrtComplexErrors::Input(..))));
477
478                    let value = Complex::new(0.0, f64::INFINITY);
479                    let result = value.try_sqrt();
480                    assert!(matches!(result, Err(SqrtComplexErrors::Input(..))));
481
482                    let value = Complex::new(f64::MIN_POSITIVE / 2.0, 0.0);
483                    let result = value.try_sqrt();
484                    assert!(matches!(result, Err(SqrtComplexErrors::Input(..))));
485
486                    let value = Complex::new(0., f64::MIN_POSITIVE / 2.0);
487                    let result = value.try_sqrt();
488                    assert!(matches!(result, Err(SqrtComplexErrors::Input(..))));
489                }
490            }
491        }
492
493        #[cfg(feature = "rug")]
494        mod rug53 {
495            use super::*;
496            use crate::backends::rug::validated::{ComplexRugStrictFinite, RealRugStrictFinite};
497
498            mod real {
499                use super::*;
500
501                #[test]
502                fn test_rug_float_sqrt_valid() {
503                    let value =
504                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 4.0)).unwrap();
505                    let expected_result =
506                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, 2.0)).unwrap();
507
508                    assert_eq!(value.clone().try_sqrt().unwrap(), expected_result);
509                    assert_eq!(value.sqrt(), expected_result);
510                }
511
512                #[test]
513                fn test_rug_float_sqrt_negative_value() {
514                    let value =
515                        RealRugStrictFinite::<53>::try_new(rug::Float::with_val(53, -4.0)).unwrap();
516                    let result = value.try_sqrt();
517                    assert!(matches!(result, Err(SqrtRealErrors::Input(..))));
518                }
519            }
520
521            mod complex {
522                use super::*;
523
524                #[test]
525                fn test_complex_rug_float_sqrt_valid() {
526                    let value = ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
527                        53,
528                        (rug::Float::with_val(53, 4.0), rug::Float::with_val(53, 0.0)),
529                    ))
530                    .unwrap();
531
532                    let expected_result =
533                        ComplexRugStrictFinite::<53>::try_new(rug::Complex::with_val(
534                            53,
535                            (rug::Float::with_val(53, 2.0), rug::Float::with_val(53, 0.0)),
536                        ))
537                        .unwrap();
538
539                    assert_eq!(value.clone().try_sqrt().unwrap(), expected_result);
540                    assert_eq!(value.sqrt(), expected_result);
541                }
542            }
543        }
544    }
545}