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