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 {}