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::policies::{Native64RawRealStrictFinitePolicy, validate_in_debug};
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/// Specialized implementation of [`Max`] and [`Min`] for `f64
109#[duplicate_item(
110    trait_name func_by_ref  func_by_value  implementation;
111    [Max]      [max_by_ref] [max_by_value] [if self >= other { self } else { other }];
112    [Min]      [min_by_ref] [min_by_value] [if self <= other { self } else { other }];
113)]
114// Specialized implementation for f64 to add debug-only validation.
115impl trait_name for f64 {
116    fn func_by_ref<'a>(&'a self, other: &'a Self) -> &'a Self {
117        // These calls are compiled out in release builds, incurring no performance cost.
118        validate_in_debug::<Native64RawRealStrictFinitePolicy>(self, "left");
119        validate_in_debug::<Native64RawRealStrictFinitePolicy>(other, "right");
120
121        implementation
122    }
123
124    fn func_by_value(self, other: Self) -> Self {
125        // These calls are compiled out in release builds, incurring no performance cost.
126        validate_in_debug::<Native64RawRealStrictFinitePolicy>(&self, "left");
127        validate_in_debug::<Native64RawRealStrictFinitePolicy>(&other, "right");
128
129        implementation
130    }
131}
132//------------------------------------------------------------------------------
133
134//------------------------------------------------------------------------------
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    mod max {
140        use super::*;
141
142        mod native64 {
143            use super::*;
144
145            #[test]
146            fn test_f64_max_valid() {
147                let a = 3.0;
148                let b = 5.0;
149                let expected_result = 5.0;
150                assert_eq!(Max::max_by_ref(&a, &b), &expected_result);
151                assert_eq!(Max::max_by_value(a, b), expected_result);
152            }
153
154            #[test]
155            fn test_f64_max_equal_values() {
156                let a = 3.0;
157                let b = 3.0;
158                let expected_result = 3.0;
159                assert_eq!(Max::max_by_ref(&a, &b), &expected_result);
160                assert_eq!(Max::max_by_value(a, b), expected_result);
161            }
162
163            #[test]
164            fn test_f64_max_negative_values() {
165                let a = -3.0;
166                let b = -5.0;
167                let expected_result = -3.0;
168                assert_eq!(Max::max_by_ref(&a, &b), &expected_result);
169                assert_eq!(Max::max_by_value(a, b), expected_result);
170            }
171
172            #[test]
173            #[cfg(debug_assertions)]
174            #[should_panic(expected = "Debug validation of left value failed: Value is NaN")]
175            fn test_f64_max_nan_value() {
176                let a = f64::NAN;
177                let b = 5.0;
178                let _result = Max::max_by_ref(&a, &b);
179            }
180
181            #[test]
182            #[cfg(debug_assertions)]
183            #[should_panic(expected = "Debug validation of right value failed: Value is NaN")]
184            fn test_f64_max_nan_other() {
185                let a = 3.0;
186                let b = f64::NAN;
187                let _result = Max::max_by_ref(&a, &b);
188            }
189
190            #[test]
191            #[cfg(debug_assertions)]
192            #[should_panic(
193                expected = "Debug validation of left value failed: Value is positive infinity"
194            )]
195            fn test_f64_max_infinite_value() {
196                let a = f64::INFINITY;
197                let b = 5.0;
198                let _result = Max::max_by_ref(&a, &b);
199            }
200
201            #[test]
202            #[cfg(debug_assertions)]
203            #[should_panic(
204                expected = "Debug validation of right value failed: Value is positive infinity"
205            )]
206            fn test_f64_max_infinite_other() {
207                let a = 3.0;
208                let b = f64::INFINITY;
209                let _result = Max::max_by_ref(&a, &b);
210            }
211
212            #[test]
213            #[cfg(debug_assertions)]
214            #[should_panic(
215                expected = "Debug validation of left value failed: Value is negative infinity"
216            )]
217            fn test_f64_max_neg_infinite_value() {
218                let a = f64::NEG_INFINITY;
219                let b = 5.0;
220                let _result = Max::max_by_ref(&a, &b);
221            }
222
223            #[test]
224            #[cfg(debug_assertions)]
225            #[should_panic(
226                expected = "Debug validation of right value failed: Value is negative infinity"
227            )]
228            fn test_f64_max_neg_infinite_other() {
229                let a = 3.0;
230                let b = f64::NEG_INFINITY;
231                let _result = Max::max_by_ref(&a, &b);
232            }
233
234            #[test]
235            #[cfg(debug_assertions)]
236            #[should_panic(expected = "Debug validation of left value failed: Value is subnormal")]
237            fn test_f64_max_subnormal_value() {
238                let a = f64::MIN_POSITIVE / 2.0;
239                let b = 5.0;
240                let _result = Max::max_by_ref(&a, &b);
241            }
242
243            #[test]
244            #[cfg(debug_assertions)]
245            #[should_panic(expected = "Debug validation of right value failed: Value is subnormal")]
246            fn test_f64_max_subnormal_other() {
247                let a = 3.0;
248                let b = f64::MIN_POSITIVE / 2.0;
249                let _result = Max::max_by_ref(&a, &b);
250            }
251        }
252
253        mod native64_strict_finite {
254            use try_create::TryNew;
255
256            use super::*;
257            use crate::backends::native64::validated::RealNative64StrictFinite;
258
259            #[test]
260            fn test_f64_max_validated() {
261                let a = RealNative64StrictFinite::try_new(3.0).unwrap();
262                let b = RealNative64StrictFinite::try_new(5.0).unwrap();
263                let expected_result = RealNative64StrictFinite::try_new(5.0).unwrap();
264                assert_eq!(Max::max_by_ref(&a, &b), &expected_result);
265                assert_eq!(Max::max_by_value(a, b), expected_result);
266            }
267
268            #[test]
269            fn test_f64_max_validated_equal() {
270                let a = RealNative64StrictFinite::try_new(3.0).unwrap();
271                let b = RealNative64StrictFinite::try_new(3.0).unwrap();
272                let expected_result = RealNative64StrictFinite::try_new(3.0).unwrap();
273                assert_eq!(Max::max_by_ref(&a, &b), &expected_result);
274                assert_eq!(Max::max_by_value(a, b), expected_result);
275            }
276        }
277    }
278
279    mod min {
280        use super::*;
281
282        mod native64 {
283            use super::*;
284
285            #[test]
286            fn test_f64_min_valid() {
287                let a = 3.0;
288                let b = 5.0;
289                let expected_result = 3.0;
290                assert_eq!(Min::min_by_ref(&a, &b), &expected_result);
291                assert_eq!(Min::min_by_value(a, b), expected_result);
292            }
293
294            #[test]
295            fn test_f64_min_equal_values() {
296                let a = 3.0;
297                let b = 3.0;
298                let expected_result = 3.0;
299                assert_eq!(Min::min_by_ref(&a, &b), &expected_result);
300                assert_eq!(Min::min_by_value(a, b), expected_result);
301            }
302
303            #[test]
304            fn test_f64_min_negative_values() {
305                let a = -3.0;
306                let b = -5.0;
307                let expected_result = -5.0;
308                assert_eq!(Min::min_by_ref(&a, &b), &expected_result);
309                assert_eq!(Min::min_by_value(a, b), expected_result);
310            }
311
312            #[test]
313            #[cfg(debug_assertions)]
314            #[should_panic(expected = "Debug validation of left value failed: Value is NaN")]
315            fn test_f64_min_nan_value() {
316                let a = f64::NAN;
317                let b = 5.0;
318                let _result = Min::min_by_ref(&a, &b);
319            }
320
321            #[test]
322            #[cfg(debug_assertions)]
323            #[should_panic(expected = "Debug validation of right value failed: Value is NaN")]
324            fn test_f64_min_nan_other() {
325                let a = 3.0;
326                let b = f64::NAN;
327                let _result = Min::min_by_ref(&a, &b);
328            }
329        }
330
331        mod native64_strict_finite {
332            use try_create::TryNew;
333
334            use super::*;
335            use crate::backends::native64::validated::RealNative64StrictFinite;
336
337            #[test]
338            fn test_f64_min_validated() {
339                let a = RealNative64StrictFinite::try_new(3.0).unwrap();
340                let b = RealNative64StrictFinite::try_new(5.0).unwrap();
341                let expected_result = RealNative64StrictFinite::try_new(3.0).unwrap();
342                assert_eq!(Min::min_by_ref(&a, &b), &expected_result);
343                assert_eq!(Min::min_by_value(a, b), expected_result);
344            }
345
346            #[test]
347            fn test_f64_min_validated_equal() {
348                let a = RealNative64StrictFinite::try_new(3.0).unwrap();
349                let b = RealNative64StrictFinite::try_new(3.0).unwrap();
350                let expected_result = RealNative64StrictFinite::try_new(3.0).unwrap();
351                assert_eq!(Min::min_by_ref(&a, &b), &expected_result);
352                assert_eq!(Min::min_by_value(a, b), expected_result);
353            }
354        }
355    }
356}
357//------------------------------------------------------------------------------