Skip to main content

num_valid/functions/
max_min.rs

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