num_valid/functions/
max_min.rs

1#![deny(rustdoc::broken_intra_doc_links)]
2
3use crate::validation::{Native64RawRealStrictFinitePolicy, debug_validate};
4use duplicate::duplicate_item;
5
6/// A trait for finding the maximum of two values.
7///
8/// This trait provides a `max` method that returns a reference to the greater of
9/// two values. It is bounded by [`PartialOrd`], which allows it to handle types
10/// with partial ordering.
11///
12/// # Implementations
13///
14/// - For types that already guarantee validity, like [`RealValidated`](crate::kernels::RealValidated),
15///   the default implementation is sufficient and safe because `PartialOrd` is already correctly implemented.
16/// - A specialized implementation is provided for `f64` to add validation checks in **debug builds**,
17///   ensuring inputs are finite and normal, and panicking if they are not.
18///
19/// # Behavior with NaN
20///
21/// For `f64`, if either input is `NaN`, the comparison `self >= other`
22/// is `false`, which means `other` will be returned. Note that in debug builds,
23/// providing a `NaN` input will cause a panic due to the validation checks.
24/// `RealValidated` types should not contain `NaN` values by contract.
25pub trait Max: PartialOrd + Sized {
26    /// Returns a reference to the maximum of `self` and `other`.
27    ///
28    /// # Examples
29    ///
30    /// ```
31    /// use num_valid::functions::Max;
32    ///
33    /// assert_eq!(Max::max(&5.0, &3.0), &5.0);
34    /// assert_eq!(Max::max(&-10.0, &-5.0), &-5.0);
35    /// ```
36    fn max<'a>(&'a self, other: &'a Self) -> &'a Self {
37        if self >= other { self } else { other }
38    }
39
40    /// Returns the maximum of `self` and `other`, consuming both values.
41    ///
42    /// # Examples
43    ///
44    /// ```
45    /// use num_valid::functions::Max;
46    ///
47    /// assert_eq!(5.0f64.max_by_value(3.0), 5.0);
48    /// assert_eq!((-10.0f64).max_by_value(-5.0), -5.0);
49    /// ```
50    fn max_by_value(self, other: Self) -> Self {
51        if self >= other { self } else { other }
52    }
53}
54
55/// A trait for finding the minimum of two values.
56///
57/// This trait provides a `min` method that returns a reference to the lesser of
58/// two values. It is bounded by [`PartialOrd`].
59///
60/// # Implementations
61///
62/// - For types that already guarantee validity, like [`RealValidated`](crate::kernels::RealValidated),
63///   the default implementation is sufficient and safe.
64/// - A specialized implementation is provided for `f64` to add validation checks in **debug builds**.
65///
66/// # Behavior with NaN
67///
68/// For `f64`, if either input is `NaN`, the comparison `self <= other`
69/// is `false`, which means `other` will be returned. Note that in debug builds,
70/// providing a `NaN` input will cause a panic.
71/// `RealValidated` types should not contain `NaN` values by contract.
72pub trait Min: PartialOrd + Sized {
73    /// Returns a reference to the minimum of `self` and `other`.
74    ///
75    /// # Examples
76    ///
77    /// ```
78    /// use num_valid::functions::Min;
79    ///
80    /// assert_eq!(Min::min(&5.0, &3.0), &3.0);
81    /// assert_eq!(Min::min(&-10.0, &-5.0), &-10.0);
82    /// ```
83    fn min<'a>(&'a self, other: &'a Self) -> &'a Self {
84        if self <= other { self } else { other }
85    }
86
87    /// Returns the minimum of `self` and `other`, consuming both values.
88    ///
89    /// # Examples
90    ///
91    /// ```
92    /// use num_valid::functions::Min;
93    ///
94    /// assert_eq!(5.0f64.min_by_value(3.0), 3.0);
95    /// assert_eq!((-10.0f64).min_by_value(-5.0), -10.0);
96    /// ```
97    fn min_by_value(self, other: Self) -> Self {
98        if self <= other { self } else { other }
99    }
100}
101
102//------------------------------------------------------------------------------
103/// Specialized implementation of [`Max`] and [`Min`] for `f64
104#[duplicate_item(
105    trait_name func  func_by_value  implementation;
106    [Max]      [max] [max_by_value] [if self >= other { self } else { other }];
107    [Min]      [min] [min_by_value] [if self <= other { self } else { other }];
108)]
109// Specialized implementation for f64 to add debug-only validation.
110impl trait_name for f64 {
111    fn func<'a>(&'a self, other: &'a Self) -> &'a Self {
112        // These calls are compiled out in release builds, incurring no performance cost.
113        debug_validate::<Native64RawRealStrictFinitePolicy>(self, "left");
114        debug_validate::<Native64RawRealStrictFinitePolicy>(other, "right");
115
116        implementation
117    }
118
119    fn func_by_value(self, other: Self) -> Self {
120        // These calls are compiled out in release builds, incurring no performance cost.
121        debug_validate::<Native64RawRealStrictFinitePolicy>(&self, "left");
122        debug_validate::<Native64RawRealStrictFinitePolicy>(&other, "right");
123
124        implementation
125    }
126}
127//------------------------------------------------------------------------------
128
129//------------------------------------------------------------------------------
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    mod max {
135        use super::*;
136
137        mod native64 {
138            use super::*;
139
140            #[test]
141            fn test_f64_max_valid() {
142                let a = 3.0;
143                let b = 5.0;
144                let expected_result = 5.0;
145                assert_eq!(Max::max(&a, &b), &expected_result);
146                assert_eq!(Max::max_by_value(a, b), expected_result);
147            }
148
149            #[test]
150            fn test_f64_max_equal_values() {
151                let a = 3.0;
152                let b = 3.0;
153                let expected_result = 3.0;
154                assert_eq!(Max::max(&a, &b), &expected_result);
155                assert_eq!(Max::max_by_value(a, b), expected_result);
156            }
157
158            #[test]
159            fn test_f64_max_negative_values() {
160                let a = -3.0;
161                let b = -5.0;
162                let expected_result = -3.0;
163                assert_eq!(Max::max(&a, &b), &expected_result);
164                assert_eq!(Max::max_by_value(a, b), expected_result);
165            }
166
167            #[test]
168            #[cfg(debug_assertions)]
169            #[should_panic(expected = "Debug validation of left value failed: Value is NaN")]
170            fn test_f64_max_nan_value() {
171                let a = f64::NAN;
172                let b = 5.0;
173                let _result = Max::max(&a, &b);
174            }
175
176            #[test]
177            #[cfg(debug_assertions)]
178            #[should_panic(expected = "Debug validation of right value failed: Value is NaN")]
179            fn test_f64_max_nan_other() {
180                let a = 3.0;
181                let b = f64::NAN;
182                let _result = Max::max(&a, &b);
183            }
184
185            #[test]
186            #[cfg(debug_assertions)]
187            #[should_panic(
188                expected = "Debug validation of left value failed: Value is positive infinity"
189            )]
190            fn test_f64_max_infinite_value() {
191                let a = f64::INFINITY;
192                let b = 5.0;
193                let _result = Max::max(&a, &b);
194            }
195
196            #[test]
197            #[cfg(debug_assertions)]
198            #[should_panic(
199                expected = "Debug validation of right value failed: Value is positive infinity"
200            )]
201            fn test_f64_max_infinite_other() {
202                let a = 3.0;
203                let b = f64::INFINITY;
204                let _result = Max::max(&a, &b);
205            }
206
207            #[test]
208            #[cfg(debug_assertions)]
209            #[should_panic(
210                expected = "Debug validation of left value failed: Value is negative infinity"
211            )]
212            fn test_f64_max_neg_infinite_value() {
213                let a = f64::NEG_INFINITY;
214                let b = 5.0;
215                let _result = Max::max(&a, &b);
216            }
217
218            #[test]
219            #[cfg(debug_assertions)]
220            #[should_panic(
221                expected = "Debug validation of right value failed: Value is negative infinity"
222            )]
223            fn test_f64_max_neg_infinite_other() {
224                let a = 3.0;
225                let b = f64::NEG_INFINITY;
226                let _result = Max::max(&a, &b);
227            }
228
229            #[test]
230            #[cfg(debug_assertions)]
231            #[should_panic(expected = "Debug validation of left value failed: Value is subnormal")]
232            fn test_f64_max_subnormal_value() {
233                let a = f64::MIN_POSITIVE / 2.0;
234                let b = 5.0;
235                let _result = Max::max(&a, &b);
236            }
237
238            #[test]
239            #[cfg(debug_assertions)]
240            #[should_panic(expected = "Debug validation of right value failed: Value is subnormal")]
241            fn test_f64_max_subnormal_other() {
242                let a = 3.0;
243                let b = f64::MIN_POSITIVE / 2.0;
244                let _result = Max::max(&a, &b);
245            }
246        }
247
248        mod native64_strict_finite {
249            use try_create::TryNew;
250
251            use super::*;
252            use crate::kernels::native64_validated::RealNative64StrictFinite;
253
254            #[test]
255            fn test_f64_max_validated() {
256                let a = RealNative64StrictFinite::try_new(3.0).unwrap();
257                let b = RealNative64StrictFinite::try_new(5.0).unwrap();
258                let expected_result = RealNative64StrictFinite::try_new(5.0).unwrap();
259                assert_eq!(Max::max(&a, &b), &expected_result);
260                assert_eq!(Max::max_by_value(a, b), expected_result);
261            }
262
263            #[test]
264            fn test_f64_max_validated_equal() {
265                let a = RealNative64StrictFinite::try_new(3.0).unwrap();
266                let b = RealNative64StrictFinite::try_new(3.0).unwrap();
267                let expected_result = RealNative64StrictFinite::try_new(3.0).unwrap();
268                assert_eq!(Max::max(&a, &b), &expected_result);
269                assert_eq!(Max::max_by_value(a, b), expected_result);
270            }
271        }
272    }
273
274    mod min {
275        use super::*;
276
277        mod native64 {
278            use super::*;
279
280            #[test]
281            fn test_f64_min_valid() {
282                let a = 3.0;
283                let b = 5.0;
284                let expected_result = 3.0;
285                assert_eq!(Min::min(&a, &b), &expected_result);
286                assert_eq!(Min::min_by_value(a, b), expected_result);
287            }
288
289            #[test]
290            fn test_f64_min_equal_values() {
291                let a = 3.0;
292                let b = 3.0;
293                let expected_result = 3.0;
294                assert_eq!(Min::min(&a, &b), &expected_result);
295                assert_eq!(Min::min_by_value(a, b), expected_result);
296            }
297
298            #[test]
299            fn test_f64_min_negative_values() {
300                let a = -3.0;
301                let b = -5.0;
302                let expected_result = -5.0;
303                assert_eq!(Min::min(&a, &b), &expected_result);
304                assert_eq!(Min::min_by_value(a, b), expected_result);
305            }
306
307            #[test]
308            #[cfg(debug_assertions)]
309            #[should_panic(expected = "Debug validation of left value failed: Value is NaN")]
310            fn test_f64_min_nan_value() {
311                let a = f64::NAN;
312                let b = 5.0;
313                let _result = Min::min(&a, &b);
314            }
315
316            #[test]
317            #[cfg(debug_assertions)]
318            #[should_panic(expected = "Debug validation of right value failed: Value is NaN")]
319            fn test_f64_min_nan_other() {
320                let a = 3.0;
321                let b = f64::NAN;
322                let _result = Min::min(&a, &b);
323            }
324        }
325
326        mod native64_strict_finite {
327            use try_create::TryNew;
328
329            use super::*;
330            use crate::kernels::native64_validated::RealNative64StrictFinite;
331
332            #[test]
333            fn test_f64_min_validated() {
334                let a = RealNative64StrictFinite::try_new(3.0).unwrap();
335                let b = RealNative64StrictFinite::try_new(5.0).unwrap();
336                let expected_result = RealNative64StrictFinite::try_new(3.0).unwrap();
337                assert_eq!(Min::min(&a, &b), &expected_result);
338                assert_eq!(Min::min_by_value(a, b), expected_result);
339            }
340
341            #[test]
342            fn test_f64_min_validated_equal() {
343                let a = RealNative64StrictFinite::try_new(3.0).unwrap();
344                let b = RealNative64StrictFinite::try_new(3.0).unwrap();
345                let expected_result = RealNative64StrictFinite::try_new(3.0).unwrap();
346                assert_eq!(Min::min(&a, &b), &expected_result);
347                assert_eq!(Min::min_by_value(a, b), expected_result);
348            }
349        }
350    }
351}
352//------------------------------------------------------------------------------