gufo_common/
math.rs

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