cuprate_helper/
num.rs

1//! Number related
2//!
3//! `#[no_std]` compatible.
4
5//---------------------------------------------------------------------------------------------------- Use
6use core::{
7    cmp::Ordering,
8    ops::{Add, Div, Mul, Sub},
9};
10
11#[cfg(feature = "std")]
12mod rolling_median;
13
14//---------------------------------------------------------------------------------------------------- Types
15// INVARIANT: must be private.
16// Protects against outside-crate implementations.
17mod private {
18    pub trait Sealed: Copy + PartialOrd<Self> + core::fmt::Display {}
19}
20
21#[cfg(feature = "std")]
22pub use rolling_median::RollingMedian;
23
24/// Non-floating point numbers
25///
26/// This trait is sealed and is only implemented on:
27/// - [`u8`] to [`u128`] and [`usize`]
28/// - [`i8`] to [`i128`] and [`isize`]
29pub trait Number: private::Sealed {}
30macro_rules! impl_number {
31    ($($num:ty),* $(,)?) => {
32        $(
33            impl Number for $num {}
34            impl private::Sealed for $num {}
35        )*
36    };
37}
38impl_number!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize);
39
40/// Floating point numbers
41///
42/// This trait is sealed and is only implemented on:
43/// - [`f32`]
44/// - [`f64`]
45pub trait Float: private::Sealed {}
46macro_rules! impl_float {
47    ($($num:ty),* $(,)?) => {
48        $(
49            impl Float for $num {}
50            impl private::Sealed for $num {}
51        )*
52    };
53}
54impl_float!(f32, f64);
55
56//---------------------------------------------------------------------------------------------------- Free Functions
57#[inline]
58/// Returns the average of two numbers; works with at least all integral and floating point types
59///
60/// ```rust
61/// # use cuprate_helper::num::*;
62/// assert_eq!(get_mid(0,        10),       5);
63/// assert_eq!(get_mid(0.0,      10.0),     5.0);
64/// assert_eq!(get_mid(-10.0,    10.0),     0.0);
65/// assert_eq!(get_mid(i16::MIN, i16::MAX), -1);
66/// assert_eq!(get_mid(u8::MIN,  u8::MAX),  127);
67///
68/// assert!(get_mid(f32::NAN, f32::NAN).is_nan());
69/// assert!(get_mid(f32::NEG_INFINITY, f32::INFINITY).is_nan());
70/// ```
71pub fn get_mid<T>(a: T, b: T) -> T
72where
73    T: Add<Output = T> + Sub<Output = T> + Div<Output = T> + Mul<Output = T> + Copy + From<u8>,
74{
75    let two: T = 2_u8.into();
76
77    // https://github.com/monero-project/monero/blob/90294f09ae34ef96f3dea5fea544816786df87c8/contrib/epee/include/misc_language.h#L43
78    (a / two) + (b / two) + ((a - two * (a / two)) + (b - two * (b / two))) / two
79}
80
81#[inline]
82/// Gets the median from a sorted slice.
83///
84/// ```rust
85/// # use cuprate_helper::num::*;
86/// let mut vec = vec![10, 5, 1, 4, 2, 8, 9, 7, 3, 6];
87/// vec.sort();
88///
89/// assert_eq!(median(vec), 5);
90/// ```
91///
92/// # Invariant
93/// If not sorted the output will be invalid.
94#[expect(clippy::debug_assert_with_mut_call)]
95pub fn median<T>(array: impl AsRef<[T]>) -> T
96where
97    T: Add<Output = T>
98        + Sub<Output = T>
99        + Div<Output = T>
100        + Mul<Output = T>
101        + PartialOrd
102        + Copy
103        + From<u8>,
104{
105    let array = array.as_ref();
106    let len = array.len();
107
108    // TODO: use `is_sorted` when stable.
109    debug_assert!(array
110        .windows(2)
111        .try_for_each(|window| if window[0] <= window[1] {
112            Ok(())
113        } else {
114            Err(())
115        })
116        .is_ok());
117
118    let mid = len / 2;
119
120    if len == 1 {
121        return array[0];
122    }
123
124    if len % 2 == 0 {
125        get_mid(array[mid - 1], array[mid])
126    } else {
127        array[mid]
128    }
129}
130
131#[inline]
132/// Compare 2 non-`NaN` floats.
133///
134/// ```rust
135/// # use cuprate_helper::num::*;
136/// # use core::cmp::Ordering;
137/// assert_eq!(cmp_float(0.0, 1.0), Ordering::Less);
138/// assert_eq!(cmp_float(1.0, 1.0), Ordering::Equal);
139/// assert_eq!(cmp_float(2.0, 1.0), Ordering::Greater);
140///
141/// assert_eq!(cmp_float(1.0,           f32::INFINITY), Ordering::Less);
142/// assert_eq!(cmp_float(f32::INFINITY, f32::INFINITY), Ordering::Equal);
143/// assert_eq!(cmp_float(f32::INFINITY, 1.0),           Ordering::Greater);
144///
145/// assert_eq!(cmp_float(f32::NEG_INFINITY, f32::INFINITY),     Ordering::Less);
146/// assert_eq!(cmp_float(f32::NEG_INFINITY, f32::NEG_INFINITY), Ordering::Equal);
147/// assert_eq!(cmp_float(f32::INFINITY,     f32::NEG_INFINITY), Ordering::Greater);
148/// ```
149///
150/// # Panic
151/// This function panics if either floats are NaNs.
152///
153/// ```rust,should_panic
154/// # use cuprate_helper::num::*;
155/// cmp_float(0.0, f32::NAN);
156/// ```
157pub fn cmp_float<F: Float>(a: F, b: F) -> Ordering {
158    match (a <= b, a >= b) {
159        (false, true) => Ordering::Greater,
160        (true, false) => Ordering::Less,
161        (true, true) => Ordering::Equal,
162        _ => panic!("cmp_float() has failed, input: {a} - {b}"),
163    }
164}
165
166#[inline]
167/// Compare 2 floats, `NaN`'s will always return [`Ordering::Equal`].
168///
169/// ```rust
170/// # use cuprate_helper::num::*;
171/// # use core::cmp::Ordering;
172/// assert_eq!(cmp_float_nan(0.0, 1.0), Ordering::Less);
173/// assert_eq!(cmp_float_nan(1.0, 1.0), Ordering::Equal);
174/// assert_eq!(cmp_float_nan(2.0, 1.0), Ordering::Greater);
175///
176/// assert_eq!(cmp_float_nan(1.0,           f32::INFINITY), Ordering::Less);
177/// assert_eq!(cmp_float_nan(f32::INFINITY, f32::INFINITY), Ordering::Equal);
178/// assert_eq!(cmp_float_nan(f32::INFINITY, 1.0),           Ordering::Greater);
179///
180/// assert_eq!(cmp_float_nan(f32::NEG_INFINITY, f32::INFINITY),     Ordering::Less);
181/// assert_eq!(cmp_float_nan(f32::NEG_INFINITY, f32::NEG_INFINITY), Ordering::Equal);
182/// assert_eq!(cmp_float_nan(f32::INFINITY,     f32::NEG_INFINITY), Ordering::Greater);
183///
184/// assert_eq!(cmp_float_nan(f32::NAN, -0.0),              Ordering::Equal);
185/// assert_eq!(cmp_float_nan(f32::NAN, 0.0),               Ordering::Equal);
186/// assert_eq!(cmp_float_nan(f32::NAN, f32::NAN),          Ordering::Equal);
187/// assert_eq!(cmp_float_nan(f32::NAN, f32::INFINITY),     Ordering::Equal);
188/// assert_eq!(cmp_float_nan(f32::NAN, f32::NEG_INFINITY), Ordering::Equal);
189/// ```
190pub fn cmp_float_nan<F: Float>(a: F, b: F) -> Ordering {
191    match (a <= b, a >= b) {
192        (false, true) => Ordering::Greater,
193        (true, false) => Ordering::Less,
194        _ => Ordering::Equal,
195    }
196}
197
198//---------------------------------------------------------------------------------------------------- Tests
199#[cfg(test)]
200mod test {}