af_utilities/types/
fixed.rs1use 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
18impl 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
50impl 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 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 fn div(self, rhs: Self) -> Self::Output {
94 Self((self.0 * Self::one().0) / rhs.0)
95 }
96}
97
98impl 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 pub fn ceil(self) -> Self {
181 if self.decimal() > U256::zero() {
182 self.trunc() + Self::one()
183 } else {
184 self
185 }
186 }
187
188 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}