Skip to main content

gufo_common/
math.rs

1//! Convienet utils for checked math
2
3#[derive(Debug, thiserror::Error, Clone, Copy)]
4pub enum MathError {
5    #[error("Operation {0:?} + {1:?} failed")]
6    AddFailed(Option<i128>, Option<i128>),
7    #[error("Operation {0:?} - {1:?} failed")]
8    SubFailed(Option<i128>, Option<i128>),
9    #[error("Operation {0:?} * {1:?} failed")]
10    MulFailed(Option<i128>, Option<i128>),
11    #[error("Operation {0:?} / {1:?} failed")]
12    DivFailed(Option<i128>, Option<i128>),
13    #[error("Conversion failed for value {0:?}")]
14    ConversionFailed(Option<i128>),
15    #[error("Division {0:?} / {1:?} not finite")]
16    DivisionNotFinite(Option<f64>, Option<f64>),
17    #[error("Negation overflowed for {0:?}")]
18    NegationOverflow(Option<i128>),
19}
20
21/// Container for safe integers operators
22///
23/// ```
24/// # use gufo_common::math::Checked;
25/// let x = Checked::new(2_u32);
26///
27/// assert_eq!((x + 3).unwrap(), 5);
28/// assert!(matches!((x + 4).check(), Ok(6)));
29/// assert!((x + u32::MAX).is_err());
30/// ```
31#[derive(Debug, Clone, Copy)]
32pub struct Checked<T>(Result<T, MathError>);
33
34impl<T> Checked<T> {
35    pub fn new(val: T) -> Self {
36        Self(Ok(val))
37    }
38
39    /// Returns the result of the calculation
40    pub fn check(self) -> Result<T, MathError> {
41        self.0
42    }
43}
44
45impl<T> From<T> for Checked<T> {
46    fn from(val: T) -> Self {
47        Self(Ok(val))
48    }
49}
50
51impl<T> std::ops::Deref for Checked<T> {
52    type Target = Result<T, MathError>;
53
54    fn deref(&self) -> &Self::Target {
55        &self.0
56    }
57}
58
59pub fn cheq<T>(val: T) -> Checked<T> {
60    Checked::new(val)
61}
62
63#[macro_export]
64/**
65 * Redefines variables as [`Checked`].
66 *
67 * ```
68 * use gufo_common::math::checked;
69 *
70 * let x = 1_u32;
71 * let y = 2_u32;
72 * checked![x];
73 *
74 * assert_eq!((x + y).unwrap(), 3);
75 *
76 * let x = 1_u32;
77 * let y = 2_u32;
78 * checked![y];
79 *
80 * assert_eq!((x + y).unwrap(), 3);
81 *
82 * let x = 5_u32;
83 * let y = 2_u32;
84 * checked![x, y,];
85 *
86 * assert_eq!((x * y).unwrap(), 10);
87 *
88 * let x = u32::MAX;
89 * let y = 1;
90 * checked![x, y,];
91 *
92 * assert!((x + y).is_err());
93 * ```
94 */
95macro_rules! checked [
96    ($($v:ident$(,)?)*) => {
97        $( let $v = $crate::math::Checked::new($v); )*
98    };
99];
100
101/// Redefines variables as mutable [`Checked`].
102#[doc(hidden)]
103#[macro_export]
104macro_rules! mut_checked [
105    ($v:ident) => {
106        let mut $v = $crate::math::Checked::new($v);
107    };
108    ($($v:ident,)*) => {
109        $( let mut $v = $crate::math::Checked::new($v); )*
110    };
111];
112
113pub use checked;
114pub use mut_checked;
115
116macro_rules! impl_operator {
117    ($op:ident, $f:ident, $t:ty) => {
118        paste::paste! {
119            impl [< Safe $op >] for $t {
120                fn [< safe_ $f >](self, rhs: $t) -> Result<$t, MathError> {
121                    let err = || MathError:: [< $op Failed >] (self.try_into().ok(), rhs.try_into().ok());
122                    self.[< checked_ $f >](rhs)
123                        .ok_or_else(err)
124                }
125            }
126        }
127
128        impl<R: Into<Self> + Copy> std::ops::$op<R> for Checked<$t>
129        {
130            type Output = Self;
131
132            #[inline]
133            fn $f(self, rhs: R) -> Self::Output {
134                let Checked(Ok(x)) = self else { return self };
135                let Checked(Ok(y)) = rhs.into() else { return rhs.into() };
136                paste::paste! {
137                let res = x.[< safe_ $f >](y);
138                }
139                Checked(res)
140            }
141        }
142
143        impl std::ops::$op<Checked<$t>> for $t
144        {
145            type Output = Checked<$t>;
146
147            #[inline]
148            fn $f(self, rhs: Checked<$t>) -> Self::Output {
149                let y = match rhs.0 {
150                    Err(err) => return Checked(Err(err)),
151                    Ok(y) => y.try_into(),
152                };
153                let y: $t = match y {
154                    Err(_) => return Checked(Err(MathError::ConversionFailed(Some(0)))),
155                    Ok(y) => y,
156                };
157                paste::paste! {
158                let res = self.[< safe_ $f >](y);
159                }
160                Checked(res)
161            }
162        }
163    };
164}
165
166macro_rules! impl_binary_operators {
167    ($t:ty) => {
168        impl_operator!(Add, add, $t);
169        impl_operator!(Sub, sub, $t);
170        impl_operator!(Mul, mul, $t);
171        impl_operator!(Div, div, $t);
172    };
173}
174
175macro_rules! impl_cast {
176    ($t:ty, $target:ident) => {
177        impl Checked<$t> {
178            pub fn $target(self) -> Checked<$target> {
179                let x = match self.0 {
180                    Err(err) => return Checked(Err(err)),
181                    Ok(v) => v,
182                };
183                Checked(
184                    x.try_into()
185                        .map_err(|_| MathError::ConversionFailed(x.try_into().ok())),
186                )
187            }
188        }
189    };
190}
191
192macro_rules! impl_casts {
193    ($t:ty) => {
194        impl_cast!($t, u8);
195        impl_cast!($t, u16);
196        impl_cast!($t, u32);
197        impl_cast!($t, u64);
198        impl_cast!($t, i16);
199        impl_cast!($t, i32);
200        impl_cast!($t, i64);
201        impl_cast!($t, usize);
202
203        impl ToU8 for $t {}
204        impl ToU16 for $t {}
205        impl ToU32 for $t {}
206        impl ToU64 for $t {}
207        impl ToI64 for $t {}
208        impl ToUsize for $t {}
209    };
210}
211
212impl_binary_operators!(u8);
213impl_binary_operators!(u16);
214impl_binary_operators!(u32);
215impl_binary_operators!(u64);
216impl_binary_operators!(i16);
217impl_binary_operators!(i32);
218impl_binary_operators!(i64);
219impl_binary_operators!(usize);
220
221impl_casts!(u8);
222impl_casts!(u16);
223impl_casts!(u32);
224impl_casts!(u64);
225impl_casts!(i16);
226impl_casts!(i32);
227impl_casts!(i64);
228impl_casts!(usize);
229
230pub trait ToU8: Sized + TryInto<u8> + TryInto<i128> + Copy {
231    fn u8(self) -> Result<u8, MathError> {
232        self.try_into()
233            .map_err(|_| MathError::ConversionFailed(self.try_into().ok()))
234    }
235}
236
237pub trait ToU16: Sized + TryInto<u16> + TryInto<i128> + Copy {
238    fn u16(self) -> Result<u16, MathError> {
239        self.try_into()
240            .map_err(|_| MathError::ConversionFailed(self.try_into().ok()))
241    }
242}
243
244pub trait ToU32: Sized + TryInto<u32> + TryInto<i128> + Copy {
245    fn u32(self) -> Result<u32, MathError> {
246        self.try_into()
247            .map_err(|_| MathError::ConversionFailed(self.try_into().ok()))
248    }
249}
250
251pub trait ToU64: Sized + TryInto<u64> + TryInto<i128> + Copy {
252    fn u64(self) -> Result<u64, MathError> {
253        self.try_into()
254            .map_err(|_| MathError::ConversionFailed(self.try_into().ok()))
255    }
256}
257
258pub trait ToI16: Sized + TryInto<i16> + TryInto<i128> + Copy {
259    fn i16(self) -> Result<i16, MathError> {
260        self.try_into()
261            .map_err(|_| MathError::ConversionFailed(self.try_into().ok()))
262    }
263}
264
265pub trait ToI32: Sized + TryInto<i32> + TryInto<i128> + Copy {
266    fn i32(self) -> Result<i32, MathError> {
267        self.try_into()
268            .map_err(|_| MathError::ConversionFailed(self.try_into().ok()))
269    }
270}
271
272pub trait ToI64: Sized + TryInto<i64> + TryInto<i128> + Copy {
273    fn i64(self) -> Result<i64, MathError> {
274        self.try_into()
275            .map_err(|_| MathError::ConversionFailed(self.try_into().ok()))
276    }
277}
278
279pub trait ToUsize: Sized + TryInto<usize> + TryInto<i128> + Copy {
280    fn usize(self) -> Result<usize, MathError> {
281        self.try_into()
282            .map_err(|_| MathError::ConversionFailed(self.try_into().ok()))
283    }
284}
285
286/// Same as `checked_add` functions but returns an error
287pub trait SafeAdd: Sized {
288    fn safe_add(self, rhs: Self) -> Result<Self, MathError>;
289}
290
291/// Same as `checked_sub` functions but returns an error
292pub trait SafeSub: Sized {
293    fn safe_sub(self, rhs: Self) -> Result<Self, MathError>;
294}
295
296/// Same as `checked_mul` functions but returns an error
297pub trait SafeMul: Sized {
298    fn safe_mul(self, rhs: Self) -> Result<Self, MathError>;
299}
300
301/// Same as `checked_dev` functions but returns an error
302pub trait SafeDiv: Sized {
303    fn safe_div(self, rhs: Self) -> Result<Self, MathError>;
304}
305
306impl SafeDiv for f64 {
307    fn safe_div(self, rhs: Self) -> Result<Self, MathError> {
308        let value = self / rhs;
309
310        if value.is_infinite() {
311            Err(MathError::DivisionNotFinite(Some(self), Some(rhs)))
312        } else {
313            Ok(value)
314        }
315    }
316}
317
318/// Same as `checked_neg` functions but returns an error
319pub trait SafeNeg: Sized {
320    fn safe_neg(self) -> Result<Self, MathError>;
321}
322
323impl SafeNeg for i64 {
324    fn safe_neg(self) -> Result<Self, MathError> {
325        self.checked_neg()
326            .ok_or_else(|| MathError::NegationOverflow(Some(self.into())))
327    }
328}
329
330/// Converts and APEX value to an F-Number
331///
332/// <https://en.wikipedia.org/wiki/APEX_system>
333pub fn apex_to_f_number(apex: f32) -> f32 {
334    f32::sqrt(1.4).powf(apex)
335}