af_utilities/types/
fixed.rs

1use std::num::ParseFloatError;
2use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Sub, SubAssign};
3use std::str::FromStr;
4
5use af_sui_types::u256::U256;
6use num_traits::{One, Zero};
7use serde::{Deserialize, Serialize};
8use thiserror::Error;
9
10use super::IFixed;
11use crate::types::errors::Error;
12
13const ONE_FIXED_F64: f64 = 1_000_000_000_000_000_000.0;
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
16pub struct Fixed(U256);
17
18// Inspired by:
19// https://docs.rs/fixed-point/latest/src/fixed_point/lib.rs.html#142-177
20impl std::fmt::Display for Fixed {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        let mut decimal = self.decimal();
23        if Self::DECIMALS == 0 || decimal == U256::zero() {
24            return write!(f, "{}.0", self.integer());
25        }
26        let mut length = Self::DECIMALS;
27        while decimal % 10u8.into() == U256::zero() {
28            decimal /= 10u8.into();
29            length -= 1;
30        }
31        let integer = self.integer();
32        write!(
33            f,
34            "{}.{:0length$}",
35            integer,
36            decimal,
37            length = length as usize
38        )
39    }
40}
41
42#[derive(Debug, Clone, Error)]
43pub enum FromStrErr {
44    #[error("Handling af-utilities types")]
45    AfUtils(#[from] Error),
46    #[error("Parsing f64")]
47    Fromf64(#[from] ParseFloatError),
48}
49
50// FIXME: currently doing str -> IFixed -> Fixed, but this will reject large positive number which
51// IFixed can't represent but Fixed can
52impl FromStr for Fixed {
53    type Err = super::FromStrRadixError;
54
55    fn from_str(s: &str) -> Result<Self, Self::Err> {
56        let signed: IFixed = s.parse()?;
57        signed.try_into().map_err(|e| super::FromStrRadixError {
58            string: s.to_owned(),
59            error: format!("{e:?}"),
60        })
61    }
62}
63
64impl Add for Fixed {
65    type Output = Self;
66
67    fn add(self, rhs: Self) -> Self::Output {
68        Self(self.0 + rhs.0)
69    }
70}
71
72impl Sub for Fixed {
73    type Output = Self;
74
75    fn sub(self, rhs: Self) -> Self::Output {
76        Self(self.0 - rhs.0)
77    }
78}
79
80impl Mul for Fixed {
81    type Output = Self;
82
83    /// This is the '`mul_down`' equivalent
84    fn mul(self, rhs: Self) -> Self::Output {
85        Self((self.0 * rhs.0) / Self::one().0)
86    }
87}
88
89impl Div for Fixed {
90    type Output = Self;
91
92    /// This is the '`div_down`' equivalent
93    fn div(self, rhs: Self) -> Self::Output {
94        Self((self.0 * Self::one().0) / rhs.0)
95    }
96}
97
98/// The remainder from the division of two fixed, inspired by the primitive floats implementations.
99///
100/// The remainder has the same sign as the dividend and is computed as:
101/// `x - (x / y).trunc() * y`.
102impl Rem for Fixed {
103    type Output = Self;
104
105    fn rem(self, rhs: Self) -> Self::Output {
106        self - (self / rhs).trunc() * rhs
107    }
108}
109
110super::reuse_op_for_assign!(Fixed {
111    AddAssign add_assign +,
112    SubAssign sub_assign -,
113    MulAssign mul_assign *,
114    DivAssign div_assign /,
115    RemAssign rem_assign %,
116});
117
118impl One for Fixed {
119    fn one() -> Self {
120        Self::one()
121    }
122}
123
124impl Zero for Fixed {
125    fn zero() -> Self {
126        Self::zero()
127    }
128
129    fn is_zero(&self) -> bool {
130        self.0 == U256::zero()
131    }
132}
133
134macro_rules! impl_from_integer {
135    ($($int:ty)*) => {
136        $(
137            impl From<$int> for Fixed {
138                fn from(value: $int) -> Self {
139                    Self(Self::one().0 * U256::from(value))
140                }
141            }
142        )*
143    };
144}
145
146impl_from_integer!(u8 u16 u32 u64 u128);
147
148macro_rules! impl_try_into_integer {
149    ($($int:ty)*) => {
150        $(
151            impl TryFrom<Fixed> for $int {
152                type Error = Error;
153
154                fn try_from(value: Fixed) -> Result<Self, Self::Error> {
155                    value.integer().try_into().map_err(|_| Error::Overflow)
156                }
157            }
158        )*
159    };
160}
161
162impl_try_into_integer!(u8 u16 u32 u64 u128);
163
164impl From<f64> for Fixed {
165    fn from(value: f64) -> Self {
166        Self(U256::from_f64_lossy(value * ONE_FIXED_F64))
167    }
168}
169
170impl From<Fixed> for f64 {
171    fn from(value: Fixed) -> Self {
172        value.0.to_f64_lossy() / ONE_FIXED_F64
173    }
174}
175
176impl Fixed {
177    const DECIMALS: u8 = 18;
178
179    /// Round this number up to an integer
180    pub fn ceil(self) -> Self {
181        if self.decimal() > U256::zero() {
182            self.trunc() + Self::one()
183        } else {
184            self
185        }
186    }
187
188    /// Truncate the decimal part of this number.
189    pub fn trunc(self) -> Self {
190        Self(self.integer() * Self::one().0)
191    }
192
193    fn decimal(&self) -> U256 {
194        self.0 % Self::one().0
195    }
196
197    fn integer(&self) -> U256 {
198        self.0 / Self::one().0
199    }
200
201    pub fn one() -> Self {
202        Self(1_000_000_000_000_000_000_u64.into())
203    }
204
205    pub const fn zero() -> Self {
206        Self(U256::zero())
207    }
208
209    pub const fn into_inner(self) -> U256 {
210        self.0
211    }
212
213    pub const fn from_inner(value: U256) -> Self {
214        Self(value)
215    }
216}
217
218#[cfg(test)]
219#[allow(clippy::unwrap_used)]
220mod tests {
221    use proptest::prelude::*;
222
223    use super::*;
224
225    #[test]
226    fn from_max_u128_doesnt_panic() {
227        let _: Fixed = u128::MAX.into();
228    }
229
230    proptest! {
231        #[test]
232        fn uint_conversions_are_preserving(x in 0..=u128::MAX) {
233            let x_: u128 = Fixed::from(x).try_into().unwrap();
234            assert_eq!(x, x_)
235        }
236
237        #[test]
238        fn can_recover_from_decimal_and_integer(x in 0..=u128::MAX, y in 1..=u128::MAX) {
239            let x: Fixed = x.into();
240            let y: Fixed = y.into();
241            let z = x / y;
242            assert_eq!(z, Fixed::from_inner(z.integer() * Fixed::one().into_inner() + z.decimal()))
243        }
244
245        #[test]
246        fn trunc_is_le_to_original(x in 0..=u128::MAX, y in 1..=u128::MAX) {
247            let x: Fixed = x.into();
248            let y: Fixed = y.into();
249            let z = x / y;
250            assert!(z.trunc() <= z)
251        }
252
253        #[test]
254        fn ceil_is_ge_to_original(x in 0..=u128::MAX, y in 1..=u128::MAX) {
255            let x: Fixed = x.into();
256            let y: Fixed = y.into();
257            let z = x / y;
258            assert!(z.ceil() >= z)
259        }
260    }
261}