num_valid/functions/
real.rs

1#![deny(rustdoc::broken_intra_doc_links)]
2
3//! Real number-specific operations and traits.
4//!
5//! This module provides traits for operations specific to real numbers, including
6//! clamping, classification, rounding, sign manipulation, and total ordering.
7
8use std::{cmp::Ordering, num::FpCategory};
9
10/// Trait for clamping a value between minimum and maximum bounds.
11///
12/// This trait provides functionality to restrict a value to lie within a specified range.
13/// If the value is less than the minimum, it returns the minimum. If greater than the
14/// maximum, it returns the maximum. Otherwise, it returns the value unchanged.
15///
16/// # Examples
17///
18/// ```
19/// use num_valid::functions::Clamp;
20///
21/// let result = Clamp::clamp_ref(5.0f64, &0.0, &10.0);
22/// assert_eq!(result, 5.0);
23///
24/// let result = Clamp::clamp_ref(-5.0f64, &0.0, &10.0);
25/// assert_eq!(result, 0.0);
26///
27/// let result = Clamp::clamp_ref(15.0f64, &0.0, &10.0);
28/// assert_eq!(result, 10.0);
29/// ```
30pub trait Clamp {
31    /// Clamp the value within the specified bounds.
32    ///
33    /// Returns `max` if `self` is greater than `max`, and `min` if `self` is less than `min`.
34    /// Otherwise this returns `self`.
35    ///
36    /// Note that this function returns `NaN` if the initial value was `NaN` as well.
37    ///
38    /// # Panics
39    /// Panics if `min` > `max`, `min` is `NaN`, or `max` is `NaN`.
40    /// ```
41    /// use num_valid::functions::Clamp;
42    ///
43    /// assert!(Clamp::clamp_ref(-3.0f64, &-2.0, &1.0) == -2.0);
44    /// assert!(Clamp::clamp_ref(0.0f64, &-2.0, &1.0) == 0.0);
45    /// assert!(Clamp::clamp_ref(2.0f64, &-2.0, &1.0) == 1.0);
46    /// assert!(Clamp::clamp_ref(f64::NAN, &-2.0, &1.0).is_nan());
47    /// ```
48    fn clamp_ref(self, min: &Self, max: &Self) -> Self;
49}
50
51/// Trait for classifying floating-point values into categories.
52///
53/// This trait provides functionality to determine the category of a floating-point number
54/// according to the IEEE 754 standard (normal, subnormal, zero, infinite, or NaN).
55///
56/// # Examples
57///
58/// ```
59/// use num_valid::functions::Classify;
60/// use std::num::FpCategory;
61///
62/// let normal = 42.0f64;
63/// assert_eq!(Classify::classify(&normal), FpCategory::Normal);
64///
65/// let infinity = f64::INFINITY;
66/// assert_eq!(Classify::classify(&infinity), FpCategory::Infinite);
67///
68/// let zero = 0.0f64;
69/// assert_eq!(Classify::classify(&zero), FpCategory::Zero);
70/// ```
71pub trait Classify {
72    /// Returns the floating point category of the number. If only one property is going to be tested,
73    /// it is generally faster to use the specific predicate instead.
74    /// ```
75    /// use num_valid::functions::Classify;
76    /// use std::num::FpCategory;
77    ///
78    /// let num = 12.4_f64;
79    /// let inf = f64::INFINITY;
80    ///
81    /// assert_eq!(Classify::classify(&num), FpCategory::Normal);
82    /// assert_eq!(Classify::classify(&inf), FpCategory::Infinite);
83    /// ```
84    fn classify(&self) -> FpCategory;
85}
86
87/// Trait for computing `exp(x) - 1` with high precision for small values.
88///
89/// This trait provides the `exp_m1` function, which computes `e^x - 1` more accurately
90/// than `exp(x) - 1` when `x` is close to zero. This avoids catastrophic cancellation
91/// that occurs when subtracting two nearly equal numbers.
92///
93/// # Mathematical Background
94///
95/// For small values of `x`, computing `exp(x) - 1` directly can lose precision because
96/// `exp(x)` is close to 1. The `exp_m1` function uses alternative algorithms (such as
97/// Taylor series) that maintain accuracy in this regime.
98///
99/// # Examples
100///
101/// ```
102/// use num_valid::functions::ExpM1;
103///
104/// let x = 1e-10f64;
105/// let result = ExpM1::exp_m1(x);
106/// // More accurate than: x.exp() - 1.0
107/// assert!((result - x).abs() < 1e-15);
108/// ```
109pub trait ExpM1 {
110    /// Returns `e^(self) - 1`` in a way that is accurate even if the number is close to zero.
111    fn exp_m1(self) -> Self;
112}
113
114/// Trait for computing the Euclidean distance (hypotenuse) between two values.
115///
116/// This trait provides the `hypot` function, which computes `sqrt(x² + y²)` in a way
117/// that avoids overflow and underflow for intermediate calculations. This is the length
118/// of the hypotenuse of a right triangle with sides of length `|x|` and `|y|`.
119///
120/// # Mathematical Background
121///
122/// Computing `sqrt(x² + y²)` directly can overflow if `x` or `y` are very large,
123/// or underflow if they are very small. The `hypot` function uses scaling techniques
124/// to avoid these issues while maintaining numerical accuracy.
125///
126/// # Examples
127///
128/// ```
129/// use num_valid::functions::Hypot;
130///
131/// let x = 3.0f64;
132/// let y = 4.0f64;
133/// let distance = Hypot::hypot(x, &y);
134/// assert_eq!(distance, 5.0);
135/// ```
136pub trait Hypot {
137    /// Compute the distance between the origin and a point (`self`, `other`) on the Euclidean plane.
138    /// Equivalently, compute the length of the hypotenuse of a right-angle triangle with other sides having length `self.abs()` and `other.abs()`.
139    fn hypot(self, other: &Self) -> Self;
140}
141
142/// Trait for computing `ln(1 + x)` with high precision for small values.
143///
144/// This trait provides the `ln_1p` function, which computes `ln(1 + x)` more accurately
145/// than `ln(1.0 + x)` when `x` is close to zero. This avoids precision loss from adding
146/// a small number to 1 and then taking the logarithm.
147///
148/// # Mathematical Background
149///
150/// For small values of `x`, computing `ln(1 + x)` directly loses precision because
151/// `1 + x` rounds to 1 in floating-point arithmetic. The `ln_1p` function uses
152/// alternative algorithms (such as Taylor series) that maintain accuracy for small `x`.
153///
154/// # Examples
155///
156/// ```
157/// use num_valid::functions::Ln1p;
158///
159/// let x = 1e-10f64;
160/// let result = Ln1p::ln_1p(x);
161/// // More accurate than: (1.0 + x).ln()
162/// assert!((result - x).abs() < 1e-15);
163/// ```
164pub trait Ln1p {
165    /// Returns `ln(1. + self)` (natural logarithm) more accurately than if the operations were performed separately.
166    fn ln_1p(self) -> Self;
167}
168
169/// Trait for total ordering comparison of floating-point values.
170///
171/// This trait provides a total ordering for floating-point numbers, including special
172/// values like NaN and signed zeros. Unlike the standard `PartialOrd` trait, this
173/// comparison always returns a definite ordering.
174///
175/// # Ordering Rules
176///
177/// The total ordering is defined as:
178/// - Negative NaN < negative infinity < negative numbers < -0.0 < +0.0 < positive numbers < positive infinity < positive NaN
179/// - NaN values are ordered by their bit representation
180/// - -0.0 is ordered as less than +0.0
181///
182/// This matches the IEEE 754-2008 `totalOrder` predicate.
183///
184/// # Examples
185///
186/// ```
187/// use num_valid::functions::TotalCmp;
188/// use std::cmp::Ordering;
189///
190/// let a = -0.0f64;
191/// let b = 0.0f64;
192/// assert_eq!(TotalCmp::total_cmp(&a, &b), Ordering::Less);
193///
194/// let nan = f64::NAN;
195/// let inf = f64::INFINITY;
196/// assert_eq!(TotalCmp::total_cmp(&inf, &nan), Ordering::Less);
197/// ```
198pub trait TotalCmp {
199    /// Compares two values using IEEE 754 total ordering.
200    ///
201    /// This ordering is defined for all floating-point values, including NaN.
202    /// The ordering is: `-NaN < -Inf < ... < -0 < +0 < ... < +Inf < +NaN`.
203    fn total_cmp(&self, other: &Self) -> Ordering;
204}