gmsol_model/
num.rs

1use std::fmt;
2
3use num_traits::{
4    CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedSub, FromPrimitive, One, Signed,
5};
6
7/// Num trait used in GMX.
8pub trait Num:
9    num_traits::Num
10    + CheckedAdd
11    + CheckedMul
12    + CheckedSub
13    + CheckedNeg
14    + CheckedDiv
15    + Clone
16    + Ord
17    + FromPrimitive
18    + fmt::Debug
19    + fmt::Display
20{
21}
22
23impl<
24        T: num_traits::Num
25            + CheckedAdd
26            + CheckedMul
27            + CheckedSub
28            + CheckedNeg
29            + CheckedDiv
30            + Clone
31            + Ord
32            + FromPrimitive
33            + fmt::Debug
34            + fmt::Display,
35    > Num for T
36{
37}
38
39/// Unsigned value that cannot be negative.
40pub trait Unsigned: num_traits::Unsigned {
41    /// The signed type.
42    type Signed: TryFrom<Self> + UnsignedAbs<Unsigned = Self> + CheckedNeg;
43
44    /// Convert to a signed value
45    fn to_signed(&self) -> crate::Result<Self::Signed>
46    where
47        Self: Clone,
48    {
49        self.clone().try_into().map_err(|_| crate::Error::Convert)
50    }
51
52    /// Convert to a signed value with the given sign.
53    fn to_signed_with_sign(&self, negative: bool) -> crate::Result<Self::Signed>
54    where
55        Self: Clone,
56        Self::Signed: CheckedSub,
57    {
58        if negative {
59            self.to_opposite_signed()
60        } else {
61            self.to_signed()
62        }
63    }
64
65    /// Convert to opposite signed.
66    fn to_opposite_signed(&self) -> crate::Result<Self::Signed>
67    where
68        Self: Clone,
69        Self::Signed: CheckedSub,
70    {
71        self.to_signed()?
72            .checked_neg()
73            .ok_or(crate::Error::Computation("to opposite signed"))
74    }
75
76    /// Compute the absolute difference of two values.
77    fn diff(self, other: Self) -> Self;
78
79    /// Compute signed `self - other`.
80    fn checked_signed_sub(self, other: Self) -> crate::Result<Self::Signed>
81    where
82        Self: Ord + Clone,
83        Self::Signed: CheckedSub,
84    {
85        if self >= other {
86            self.diff(other).to_signed()
87        } else {
88            self.diff(other).to_opposite_signed()
89        }
90    }
91
92    /// Checked signed add.
93    fn checked_add_with_signed(&self, other: &Self::Signed) -> Option<Self>
94    where
95        Self: CheckedAdd + CheckedSub,
96    {
97        let value = other.unsigned_abs();
98        if other.is_positive() {
99            self.checked_add(&value)
100        } else {
101            self.checked_sub(&value)
102        }
103    }
104
105    /// Checked signed sub.
106    fn checked_sub_with_signed(&self, other: &Self::Signed) -> Option<Self>
107    where
108        Self: CheckedAdd + CheckedSub,
109    {
110        let value = other.unsigned_abs();
111        if other.is_positive() {
112            self.checked_sub(&value)
113        } else {
114            self.checked_add(&value)
115        }
116    }
117
118    /// Checked signed mul.
119    fn checked_mul_with_signed(&self, other: &Self::Signed) -> Option<Self::Signed>
120    where
121        Self: CheckedMul,
122    {
123        let value = other.unsigned_abs();
124        if other.is_negative() {
125            Some(
126                Self::Signed::try_from(self.checked_mul(&value)?)
127                    .ok()?
128                    .checked_neg()?,
129            )
130        } else {
131            self.checked_mul(&value)?.try_into().ok()
132        }
133    }
134
135    /// As divisor to checked divide other and round up magnitude.
136    fn as_divisor_to_round_up_magnitude_div(&self, dividend: &Self::Signed) -> Option<Self::Signed>
137    where
138        Self: Clone,
139        Self::Signed: CheckedSub + CheckedAdd + CheckedDiv,
140    {
141        if self.is_zero() {
142            return None;
143        }
144        let divisor: Self::Signed = self.clone().try_into().ok()?;
145        if dividend.is_negative() {
146            dividend
147                .checked_sub(&divisor)?
148                .checked_add(&One::one())?
149                .checked_div(&divisor)
150        } else {
151            dividend
152                .checked_add(&divisor)?
153                .checked_sub(&One::one())?
154                .checked_div(&divisor)
155        }
156    }
157
158    /// Checked round up division.
159    fn checked_round_up_div(&self, divisor: &Self) -> Option<Self>
160    where
161        Self: CheckedAdd + CheckedSub + Clone + CheckedDiv,
162    {
163        if divisor.is_zero() {
164            return None;
165        }
166        self.checked_add(divisor)?
167            .checked_sub(&One::one())?
168            .checked_div(divisor)
169    }
170
171    /// Bound the magnitude of a signed value.
172    ///
173    /// # Errors
174    /// Return error if
175    /// - `min > max`
176    /// - `min` is greater than the maximum value representable by `Self::Signed`
177    ///
178    /// # Examples
179    ///
180    /// This method can be used to bound the magnitude of a signed value:
181    /// ```
182    /// # use gmsol_model::num::Unsigned;
183    /// let a = -123i64;
184    /// // Original value within bounds
185    /// assert_eq!(Unsigned::bound_magnitude(&a, &0, &124u64).unwrap(), -123);
186    /// // Value clamped to max magnitude
187    /// assert_eq!(Unsigned::bound_magnitude(&a, &0, &120u64).unwrap(), -120);
188    /// // Value clamped to min magnitude
189    /// assert_eq!(Unsigned::bound_magnitude(&a, &124, &256u64).unwrap(), -124);
190    ///
191    /// let b = 123i64;
192    /// // Original value within bounds
193    /// assert_eq!(Unsigned::bound_magnitude(&b, &0, &124u64).unwrap(), 123);
194    /// // Value clamped to max magnitude
195    /// assert_eq!(Unsigned::bound_magnitude(&b, &0, &120u64).unwrap(), 120);
196    /// // Value clamped to min magnitude
197    /// assert_eq!(Unsigned::bound_magnitude(&b, &124, &256u64).unwrap(), 124);
198    /// ```
199    ///
200    /// Returns an error if `min > max`:
201    /// ```
202    /// # use gmsol_model::num::Unsigned;
203    /// let result = Unsigned::bound_magnitude(&0, &1u64, &0);
204    /// assert!(result.is_err());
205    /// ```
206    ///
207    /// Returns an error if `min` is greater than the maximum value representable by `Self::Signed`:
208    /// ```
209    /// # use gmsol_model::num::Unsigned;
210    /// let result = Unsigned::bound_magnitude(&0, &(u64::MAX / 2 + 1), &u64::MAX);
211    /// assert!(result.is_err());
212    /// ```
213    fn bound_magnitude(value: &Self::Signed, min: &Self, max: &Self) -> crate::Result<Self::Signed>
214    where
215        Self: Ord + Clone,
216        Self::Signed: Clone + CheckedSub,
217    {
218        if min > max {
219            return Err(crate::Error::InvalidArgument("min > max"));
220        }
221        let magnitude = value.unsigned_abs();
222        let negative = value.is_negative();
223        if magnitude < *min {
224            min.to_signed_with_sign(negative)
225        } else if magnitude > *max {
226            max.to_signed_with_sign(negative)
227        } else {
228            Ok(value.clone())
229        }
230    }
231}
232
233/// Convert signed value to unsigned.
234pub trait UnsignedAbs: Signed {
235    /// Unsigned type.
236    type Unsigned;
237
238    /// Computes the absolute value and returns as a unsigned value.
239    fn unsigned_abs(&self) -> Self::Unsigned;
240}
241
242/// Perform Mul-Div calculation with bigger range num type.
243pub trait MulDiv: Unsigned {
244    /// Calculates floor(self * numerator / denominator) with full precision.
245    ///
246    /// Returns `None` if the `denominator` is zero or overflow.
247    fn checked_mul_div(&self, numerator: &Self, denominator: &Self) -> Option<Self>;
248
249    /// Calculates ceil(self * numerator / denominator) with full precision.
250    ///
251    /// Returns `None` if the `denominator` is zero or overflow.
252    fn checked_mul_div_ceil(&self, numerator: &Self, denominator: &Self) -> Option<Self>;
253
254    /// Calculates floor(self * numerator / denominator) with full precision,
255    /// where `numerator` is signed.
256    ///
257    /// Returns `None` if the `denominator` is zero or overflow.
258    fn checked_mul_div_with_signed_numerator(
259        &self,
260        numerator: &Self::Signed,
261        denominator: &Self,
262    ) -> Option<Self::Signed> {
263        let ans = self
264            .checked_mul_div(&numerator.unsigned_abs(), denominator)?
265            .try_into()
266            .ok()?;
267        if numerator.is_positive() {
268            Some(ans)
269        } else {
270            ans.checked_neg()
271        }
272    }
273}
274
275impl Unsigned for u64 {
276    type Signed = i64;
277
278    fn diff(self, other: Self) -> Self {
279        self.abs_diff(other)
280    }
281}
282
283impl MulDiv for u64 {
284    #[allow(clippy::arithmetic_side_effects)]
285    fn checked_mul_div(&self, numerator: &Self, denominator: &Self) -> Option<Self> {
286        if *denominator == 0 {
287            return None;
288        }
289        let x = *self as u128;
290        let numerator = *numerator as u128;
291        let denominator = *denominator as u128;
292        let ans = x * numerator / denominator;
293        ans.try_into().ok()
294    }
295
296    #[allow(clippy::arithmetic_side_effects)]
297    fn checked_mul_div_ceil(&self, numerator: &Self, denominator: &Self) -> Option<Self> {
298        if *denominator == 0 {
299            return None;
300        }
301        let x = *self as u128;
302        let numerator = *numerator as u128;
303        let denominator = *denominator as u128;
304        let ans = (x * numerator).div_ceil(denominator);
305        ans.try_into().ok()
306    }
307}
308
309impl UnsignedAbs for i64 {
310    type Unsigned = u64;
311
312    fn unsigned_abs(&self) -> u64 {
313        (*self).unsigned_abs()
314    }
315}
316
317#[cfg(feature = "u128")]
318/// Add support to `u128`.
319mod u128 {
320    use super::{MulDiv, Unsigned, UnsignedAbs};
321    use ruint::aliases::U256;
322
323    impl Unsigned for u128 {
324        type Signed = i128;
325
326        fn diff(self, other: Self) -> Self {
327            self.abs_diff(other)
328        }
329    }
330
331    impl UnsignedAbs for i128 {
332        type Unsigned = u128;
333
334        fn unsigned_abs(&self) -> u128 {
335            (*self).unsigned_abs()
336        }
337    }
338
339    impl MulDiv for u128 {
340        #[allow(clippy::arithmetic_side_effects)]
341        fn checked_mul_div(&self, numerator: &Self, denominator: &Self) -> Option<Self> {
342            if *denominator == 0 {
343                return None;
344            }
345            let x = U256::from(*self);
346            let numerator = U256::from(*numerator);
347            let denominator = U256::from(*denominator);
348            let ans = x * numerator / denominator;
349            ans.try_into().ok()
350        }
351
352        #[allow(clippy::arithmetic_side_effects)]
353        fn checked_mul_div_ceil(&self, numerator: &Self, denominator: &Self) -> Option<Self> {
354            if *denominator == 0 {
355                return None;
356            }
357            let x = U256::from(*self);
358            let numerator = U256::from(*numerator);
359            let denominator = U256::from(*denominator);
360            let ans = (x * numerator).div_ceil(denominator);
361            ans.try_into().ok()
362        }
363    }
364}
365
366#[cfg(test)]
367mod tests {
368    use super::*;
369
370    #[test]
371    fn round_up_magnitude_division() {
372        let b = 3u64;
373        let positive = 1i64;
374        let negative = -1i64;
375
376        assert_eq!(b.as_divisor_to_round_up_magnitude_div(&positive), Some(1));
377        assert_eq!(b.as_divisor_to_round_up_magnitude_div(&negative), Some(-1));
378    }
379
380    #[test]
381    fn round_up_division() {
382        let b = 3u64;
383        let a = 1u64;
384        assert_eq!(a.checked_round_up_div(&b), Some(1));
385    }
386
387    #[test]
388    fn mul_div_ceil() {
389        let a = 650_406_504u64;
390        let a2 = 650_406_505u64;
391        let b = 40_000_000_000u64;
392        let c = 80_000_000_000u64;
393        assert_eq!(a.checked_mul_div_ceil(&b, &c).unwrap(), 325_203_252);
394        assert_eq!(a2.checked_mul_div_ceil(&b, &c).unwrap(), 325_203_253);
395    }
396
397    #[cfg(feature = "u128")]
398    #[test]
399    fn mul_div_ceil_u128() {
400        let a = 650_406_504u128;
401        let a2 = 650_406_505u128;
402        let b = 40_000_000_000u128;
403        let c = 80_000_000_000u128;
404        assert_eq!(a.checked_mul_div_ceil(&b, &c).unwrap(), 325_203_252);
405        assert_eq!(a2.checked_mul_div_ceil(&b, &c).unwrap(), 325_203_253);
406    }
407
408    #[test]
409    fn bound_magnitude() {
410        let a = -123i64;
411        assert_eq!(Unsigned::bound_magnitude(&a, &0, &124u64).unwrap(), -123);
412        assert_eq!(Unsigned::bound_magnitude(&a, &0, &120u64).unwrap(), -120);
413        assert_eq!(Unsigned::bound_magnitude(&a, &124, &256u64).unwrap(), -124);
414        assert_eq!(Unsigned::bound_magnitude(&a, &125, &125u64).unwrap(), -125);
415
416        let b = 123i64;
417        assert_eq!(Unsigned::bound_magnitude(&b, &0, &124u64).unwrap(), 123);
418        assert_eq!(Unsigned::bound_magnitude(&b, &0, &120u64).unwrap(), 120);
419        assert_eq!(Unsigned::bound_magnitude(&b, &124, &256u64).unwrap(), 124);
420        assert_eq!(Unsigned::bound_magnitude(&b, &125, &125u64).unwrap(), 125);
421
422        let c = 0i64;
423        assert_eq!(Unsigned::bound_magnitude(&c, &1, &124u64).unwrap(), 1);
424
425        let d = -0i64;
426        assert_eq!(Unsigned::bound_magnitude(&d, &1, &124u64).unwrap(), 1);
427
428        let result = Unsigned::bound_magnitude(&0, &u64::MAX, &u64::MAX);
429        assert!(result.is_err());
430    }
431}