af_utilities/types/
ifixed.rs1use 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
37impl 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 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 fn div(self, rhs: Self) -> Self::Output {
195 Self((self.0 * Self::one().0) / rhs.0)
196 }
197}
198
199impl 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
252impl IFixed {
255 pub const DECIMALS: u8 = 18;
256
257 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 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 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 pub const fn from_inner(inner: I256) -> Self {
289 Self(inner)
290 }
291
292 pub fn trunc(self) -> Self {
294 Self(self.integer() * Self::one().0)
295 }
296
297 pub fn integer(self) -> I256 {
299 self.0 / Self::one().0
300 }
301
302 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 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}