af_utilities/types/
ifixed.rs

1use std::ops::{
2    Add,
3    AddAssign,
4    Div,
5    DivAssign,
6    Mul,
7    MulAssign,
8    Neg,
9    Rem,
10    RemAssign,
11    Sub,
12    SubAssign,
13};
14use std::str::FromStr;
15
16use af_sui_types::u256::U256;
17use num_traits::{One, Zero};
18use serde::{Deserialize, Serialize};
19
20use super::errors::Error;
21use super::i256::I256;
22use super::{Balance9, Fixed};
23
24#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
25pub struct IFixed(I256);
26
27impl std::fmt::Debug for IFixed {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        if f.alternate() {
30            f.debug_tuple("IFixed").field(&self.0).finish()
31        } else {
32            <Self as std::fmt::Display>::fmt(self, f)
33        }
34    }
35}
36
37// Inspired by:
38// https://docs.rs/fixed-point/latest/src/fixed_point/lib.rs.html#142-177
39impl std::fmt::Display for IFixed {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        let mut decimal = self.udecimal();
42        if Self::DECIMALS == 0 || decimal == U256::zero() {
43            return write!(f, "{}.0", self.integer());
44        }
45        let mut length = Self::DECIMALS;
46        while decimal % 10u8.into() == U256::zero() {
47            decimal /= 10u8.into();
48            length -= 1;
49        }
50        let integer = self.integer();
51        if integer == I256::zero() && self.is_neg() {
52            write!(f, "-0.{:0length$}", decimal, length = length as usize)
53        } else {
54            write!(
55                f,
56                "{}.{:0length$}",
57                integer,
58                decimal,
59                length = length as usize
60            )
61        }
62    }
63}
64
65impl Default for IFixed {
66    fn default() -> Self {
67        Self::zero()
68    }
69}
70
71impl TryFrom<Fixed> for IFixed {
72    type Error = Error;
73
74    fn try_from(value: Fixed) -> Result<Self, Self::Error> {
75        Ok(Self(value.into_inner().try_into()?))
76    }
77}
78
79impl TryFrom<IFixed> for Fixed {
80    type Error = Error;
81
82    fn try_from(value: IFixed) -> Result<Self, Self::Error> {
83        Ok(Self::from_inner(value.0.try_into()?))
84    }
85}
86
87impl FromStr for IFixed {
88    type Err = super::FromStrRadixError;
89
90    fn from_str(s: &str) -> Result<Self, Self::Err> {
91        super::from_str::ifixed_from_str(s)
92    }
93}
94
95impl TryFrom<f64> for IFixed {
96    type Error = super::FromStrRadixError;
97
98    fn try_from(value: f64) -> Result<Self, Self::Error> {
99        super::from_str::ifixed_from_str(&value.to_string())
100    }
101}
102
103impl TryFrom<IFixed> for f64 {
104    type Error = <Self as FromStr>::Err;
105
106    fn try_from(value: IFixed) -> Result<Self, Self::Error> {
107        value.to_string().parse()
108    }
109}
110
111impl From<Balance9> for IFixed {
112    fn from(value: Balance9) -> Self {
113        let balance_u256: U256 = value.into_inner().into();
114        let scaling_factor: U256 = 1_000_000_000_u64.into();
115        Self(I256::from_inner(balance_u256 * scaling_factor))
116    }
117}
118
119impl TryFrom<IFixed> for Balance9 {
120    type Error = Error;
121
122    fn try_from(value: IFixed) -> Result<Self, Self::Error> {
123        if value.is_neg() {
124            return Err(Error::Underflow);
125        }
126
127        let scaling_factor: U256 = 1_000_000_000_u64.into();
128        let inner = (value.into_inner().into_inner() / scaling_factor)
129            .try_into()
130            .map_err(|_| Error::Overflow)?;
131        Ok(Self::from_inner(inner))
132    }
133}
134
135macro_rules! impl_from_integer {
136    ($($int:ty)*) => {
137        $(
138            impl From<$int> for IFixed {
139                fn from(value: $int) -> Self {
140                    Self(Self::one().0 * I256::from(value))
141                }
142            }
143        )*
144    };
145}
146
147impl_from_integer!(u8 u16 u32 u64 u128 i8 i16 i32 i64 i128);
148
149macro_rules! impl_try_into_integer {
150    ($($int:ty)*) => {
151        $(
152            impl TryFrom<IFixed> for $int {
153                type Error = Error;
154
155                fn try_from(value: IFixed) -> Result<Self, Self::Error> {
156                    value.integer().try_into()
157                }
158            }
159        )*
160    };
161}
162
163impl_try_into_integer!(u8 u16 u32 u64 u128 i8 i16 i32 i64 i128);
164
165impl Add for IFixed {
166    type Output = Self;
167
168    fn add(self, rhs: Self) -> Self::Output {
169        Self(self.0 + rhs.0)
170    }
171}
172
173impl Sub for IFixed {
174    type Output = Self;
175
176    fn sub(self, rhs: Self) -> Self::Output {
177        Self(self.0 - rhs.0)
178    }
179}
180
181impl Mul for IFixed {
182    type Output = Self;
183
184    /// This is the '`mul_down`' equivalent
185    fn mul(self, rhs: Self) -> Self::Output {
186        Self((self.0 * rhs.0) / Self::one().0)
187    }
188}
189
190impl Div for IFixed {
191    type Output = Self;
192
193    /// This is the '`div_down`' equivalent
194    fn div(self, rhs: Self) -> Self::Output {
195        Self((self.0 * Self::one().0) / rhs.0)
196    }
197}
198
199/// The remainder from the division of two fixed, inspired by the primitive floats implementations.
200///
201/// The remainder has the same sign as the dividend and is computed as:
202/// `x - (x / y).trunc() * y`.
203///
204/// # Examples
205/// ```
206/// # use af_utilities::types::IFixed;
207/// let x: IFixed = 50.50.try_into().unwrap();
208/// let y: IFixed = 8.125.try_into().unwrap();
209/// let remainder = x - (x / y).trunc() * y;
210/// assert_eq!(x % y, IFixed::try_from(1.75).unwrap());
211/// ```
212impl Rem for IFixed {
213    type Output = Self;
214
215    fn rem(self, rhs: Self) -> Self::Output {
216        self - (self / rhs).trunc() * rhs
217    }
218}
219
220super::reuse_op_for_assign!(IFixed {
221    AddAssign add_assign +,
222    SubAssign sub_assign -,
223    MulAssign mul_assign *,
224    DivAssign div_assign /,
225    RemAssign rem_assign %,
226});
227
228impl Neg for IFixed {
229    type Output = Self;
230
231    fn neg(self) -> Self::Output {
232        Self(-self.0)
233    }
234}
235
236impl One for IFixed {
237    fn one() -> Self {
238        Self::one()
239    }
240}
241
242impl Zero for IFixed {
243    fn zero() -> Self {
244        Self::zero()
245    }
246
247    fn is_zero(&self) -> bool {
248        *self == Self::zero()
249    }
250}
251
252// TODO: add IFixed::from_pyth_repr(i64, i32)
253// https://docs.rs/pyth-sdk/0.8.0/pyth_sdk/struct.Price.html
254impl IFixed {
255    pub const DECIMALS: u8 = 18;
256
257    /// Create an `u64` from a `IFixed` applying the specified scaling factor.
258    pub fn try_into_balance_with_scaling(self, scaling_factor: U256) -> Result<u64, Error> {
259        if self.is_neg() {
260            return Err(Error::Underflow);
261        }
262
263        let inner = (self.into_inner().into_inner() / scaling_factor)
264            .try_into()
265            .map_err(|_| Error::Overflow)?;
266        Ok(inner)
267    }
268
269    /// Create an `IFixed` from a `u64` applying the specified scaling factor.
270    pub fn from_balance_with_scaling(balance: u64, scaling_factor: U256) -> Self {
271        let balance_u256: U256 = balance.into();
272        Self(I256::from_inner(balance_u256 * scaling_factor))
273    }
274
275    /// Create an `IFixed` from a `str` containing the
276    /// ifixed internal representation.
277    ///
278    /// Example: the `str` containing "134850000000000000000" is
279    /// converted to the value 134.85 in IFixed.
280    pub fn from_raw_str(ifixed_string: &str) -> Result<Self, Error> {
281        let Ok(u256_val) = ifixed_string.parse::<U256>() else {
282            return Err(Error::ParseStringToU256(ifixed_string.to_string()));
283        };
284        Ok(Self::from_inner(I256::from_inner(u256_val)))
285    }
286
287    /// Create an `IFixed` using its internal representation
288    pub const fn from_inner(inner: I256) -> Self {
289        Self(inner)
290    }
291
292    /// Truncate the decimal part of this number.
293    pub fn trunc(self) -> Self {
294        Self(self.integer() * Self::one().0)
295    }
296
297    /// The integer part of this number.
298    pub fn integer(self) -> I256 {
299        self.0 / Self::one().0
300    }
301
302    /// The decimal part of this number.
303    pub fn decimal(self) -> I256 {
304        self.0 % Self::one().0
305    }
306
307    pub fn round_to_decimals(self, decimals: u32, round_up: bool) -> Self {
308        let scaling_factor: I256 = 10_u64.pow(decimals).into();
309        let rounding: I256 = 1_u64.into();
310        let partial = self.into_inner() / scaling_factor;
311        if round_up {
312            Self((partial + rounding) * scaling_factor)
313        } else {
314            Self((partial - rounding) * scaling_factor)
315        }
316    }
317
318    /// The unsigned decimal part of this number.
319    pub fn udecimal(self) -> U256 {
320        self.0.uabs() % Self::one().0.uabs()
321    }
322
323    pub const fn into_inner(self) -> I256 {
324        self.0
325    }
326
327    pub fn is_neg(&self) -> bool {
328        self.0.is_neg()
329    }
330
331    pub fn one() -> Self {
332        Self(1_000_000_000_000_000_000_u64.into())
333    }
334
335    pub const fn zero() -> Self {
336        Self(I256::zero())
337    }
338
339    pub fn abs(self) -> Self {
340        Self(self.0.abs())
341    }
342
343    pub fn uabs(self) -> Fixed {
344        Fixed::from_inner(self.0.uabs())
345    }
346
347    pub fn copy_sign(self, other: &Self) -> Self {
348        if other.is_neg() { -self } else { self }
349    }
350}
351
352#[cfg(test)]
353#[allow(clippy::unwrap_used)]
354mod tests {
355    use proptest::prelude::*;
356
357    use super::*;
358
359    #[test]
360    fn from_u128_max_doesnt_overflow() {
361        assert!(!IFixed::from(u128::MAX).is_neg())
362    }
363
364    #[test]
365    fn from_i128_min_doesnt_underflow() {
366        assert!(IFixed::from(i128::MIN).is_neg())
367    }
368
369    proptest! {
370        #[test]
371        fn int_conversions_are_preserving(x in i128::MIN..=i128::MAX) {
372            let x_: i128 = IFixed::from(x).try_into().unwrap();
373            assert_eq!(x, x_)
374        }
375
376        #[test]
377        fn uint_conversions_are_preserving(x in 0..=u128::MAX) {
378            let x_: u128 = IFixed::from(x).try_into().unwrap();
379            assert_eq!(x, x_)
380        }
381
382        #[test]
383        fn trunc_is_le_to_original(x in i128::MIN..=i128::MAX, y in i128::MIN..=i128::MAX) {
384            let x: IFixed = x.into();
385            let y: IFixed = y.into();
386            let z = x / y;
387            assert!(z.trunc().abs() <= z.abs())
388        }
389    }
390
391    fn ifixed_to_float(s: &str) -> Result<f64, <f64 as FromStr>::Err> {
392        IFixed::from_str(s).unwrap().try_into()
393    }
394
395    #[test]
396    fn try_into_f64() {
397        let mut float = ifixed_to_float("0.001").unwrap();
398        insta::assert_snapshot!(float, @"0.001");
399
400        float = ifixed_to_float("0.009").unwrap();
401        insta::assert_snapshot!(float, @"0.009");
402
403        float = ifixed_to_float("0.003").unwrap();
404        insta::assert_snapshot!(float, @"0.003");
405
406        float = ifixed_to_float("0.000000000000000001").unwrap();
407        insta::assert_snapshot!(float, @"0.000000000000000001");
408
409        float = ifixed_to_float("2.2238").unwrap();
410        insta::assert_snapshot!(float, @"2.2238");
411
412        float = ifixed_to_float("23000000000.0").unwrap();
413        insta::assert_snapshot!(float, @"23000000000");
414
415        float = ifixed_to_float("123456700000000000000.0").unwrap();
416        insta::assert_snapshot!(float, @"123456700000000000000");
417
418        float = ifixed_to_float("2.3e+10").unwrap();
419        insta::assert_snapshot!(float, @"23000000000");
420
421        float = ifixed_to_float("1.234567e+20").unwrap();
422        insta::assert_snapshot!(float, @"123456700000000000000");
423
424        float = ifixed_to_float("-2.2238").unwrap();
425        insta::assert_snapshot!(float, @"-2.2238");
426
427        float = ifixed_to_float("-1.234567e+20").unwrap();
428        insta::assert_snapshot!(float, @"-123456700000000000000");
429
430        float = ifixed_to_float(
431            "57896044618658097711785492504343953926634992332820282019728.792003956564819967",
432        )
433        .unwrap();
434        insta::assert_snapshot!(float, @"57896044618658100000000000000000000000000000000000000000000");
435        insta::assert_debug_snapshot!(float, @"5.78960446186581e58");
436
437        float = ifixed_to_float(
438            "-57896044618658097711785492504343953926634992332820282019728.792003956564819967",
439        )
440        .unwrap();
441        insta::assert_snapshot!(float, @"-57896044618658100000000000000000000000000000000000000000000");
442        insta::assert_debug_snapshot!(float, @"-5.78960446186581e58");
443    }
444}