decimal_wad/
decimal.rs

1use std::{convert::TryFrom, fmt};
2
3use crate::common::*;
4use crate::error::*;
5use crate::rate::*;
6
7// Re-export for compatibility with pre 0.1.7 versions
8pub use crate::common::uint::U192;
9
10/// Large decimal values, precise to 18 digits
11#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Eq, Ord)]
12pub struct Decimal(pub U192);
13
14impl Decimal {
15    /// One
16    pub fn one() -> Self {
17        Self(Self::wad())
18    }
19
20    /// Zero
21    pub fn zero() -> Self {
22        Self(U192::zero())
23    }
24
25    // OPTIMIZE: use const slice when fixed in BPF toolchain
26    fn wad() -> U192 {
27        U192::from(WAD)
28    }
29
30    // OPTIMIZE: use const slice when fixed in BPF toolchain
31    fn half_wad() -> U192 {
32        U192::from(HALF_WAD)
33    }
34
35    /// Create scaled decimal from percent value
36    pub fn from_percent<T>(percent: T) -> Self
37    where
38        T: Into<U192>,
39    {
40        let percent: U192 = percent.into();
41        Self(percent.checked_mul(PERCENT_SCALER.into()).unwrap())
42    }
43
44    #[deprecated(
45        since = "0.1.7",
46        note = "please use the generic `from_percent` instead"
47    )]
48    pub fn from_percent_u64(percent: u64) -> Self {
49        Self::from_percent(percent)
50    }
51
52    /// Get percent value from a scaled decimal
53    pub fn to_percent<T>(&self) -> Result<T, DecimalError>
54    where
55        T: TryFrom<U192>,
56    {
57        T::try_from(self.0 / PERCENT_SCALER).map_err(|_| DecimalError::MathOverflow)
58    }
59
60    /// Create scaled decimal from percent value
61    pub fn to_bps<T>(&self) -> Result<T, DecimalError>
62    where
63        T: TryFrom<U192>,
64    {
65        T::try_from(self.0 / BPS_SCALER).map_err(|_| DecimalError::MathOverflow)
66    }
67
68    /// Create scaled decimal from bps value
69    pub fn from_bps(bps: impl Into<U192>) -> Self {
70        let bps: U192 = bps.into();
71        Self(bps.checked_mul(BPS_SCALER.into()).unwrap())
72    }
73
74    /// Return raw scaled value if it fits the destination type T
75    pub fn to_scaled_val<T>(&self) -> Result<T, DecimalError>
76    where
77        T: TryFrom<U192>,
78    {
79        T::try_from(self.0).map_err(|_| DecimalError::MathOverflow)
80    }
81
82    /// Create decimal from scaled value
83    pub fn from_scaled_val(scaled_val: impl Into<U192>) -> Self {
84        Self(scaled_val.into())
85    }
86
87    /// Round scaled decimal
88    pub fn try_round<T>(&self) -> Result<T, DecimalError>
89    where
90        T: TryFrom<U192>,
91    {
92        let rounded_val = Self::half_wad()
93            .checked_add(self.0)
94            .ok_or(DecimalError::MathOverflow)?
95            .checked_div(Self::wad())
96            .ok_or(DecimalError::MathOverflow)?;
97        T::try_from(rounded_val).map_err(|_| DecimalError::MathOverflow)
98    }
99
100    /// Round scaled decimal to u64
101    #[deprecated(since = "0.1.7", note = "please use the generic `try_round` instead")]
102    pub fn try_round_u64(&self) -> Result<u64, DecimalError> {
103        self.try_round()
104    }
105
106    /// Round scaled decimal to u128
107    #[deprecated(since = "0.1.7", note = "please use the generic `try_round` instead")]
108    pub fn try_round_u128(&self) -> Result<u128, DecimalError> {
109        self.try_round()
110    }
111
112    /// Ceiling scaled decimal
113    pub fn try_ceil<T>(&self) -> Result<T, DecimalError>
114    where
115        T: TryFrom<U192>,
116    {
117        let ceil_val = Self::wad()
118            .checked_sub(U192::from(1u64))
119            .ok_or(DecimalError::MathOverflow)?
120            .checked_add(self.0)
121            .ok_or(DecimalError::MathOverflow)?
122            .checked_div(Self::wad())
123            .ok_or(DecimalError::MathOverflow)?;
124        T::try_from(ceil_val).map_err(|_| DecimalError::MathOverflow)
125    }
126
127    /// Ceiling scaled decimal to u64
128    #[deprecated(since = "0.1.7", note = "please use the generic `try_ceil` instead")]
129    pub fn try_ceil_u64(&self) -> Result<u64, DecimalError> {
130        self.try_ceil()
131    }
132
133    /// Ceiling scaled decimal to u128
134    #[deprecated(since = "0.1.7", note = "please use the generic `try_ceil` instead")]
135    pub fn try_ceil_u128(&self) -> Result<u128, DecimalError> {
136        self.try_ceil()
137    }
138
139    /// Floor scaled decimal
140    pub fn try_floor<T>(&self) -> Result<T, DecimalError>
141    where
142        T: TryFrom<U192>,
143    {
144        let ceil_val = self
145            .0
146            .checked_div(Self::wad())
147            .ok_or(DecimalError::MathOverflow)?;
148        T::try_from(ceil_val).map_err(|_| DecimalError::MathOverflow)
149    }
150
151    /// Floor scaled decimal to u64
152    #[deprecated(since = "0.1.7", note = "please use the generic `try_floor` instead")]
153    pub fn try_floor_u64(&self) -> Result<u64, DecimalError> {
154        self.try_floor()
155    }
156
157    /// Floor scaled decimal to u128
158    #[deprecated(since = "0.1.7", note = "please use the generic `try_floor` instead")]
159    pub fn try_floor_u128(&self) -> Result<u128, DecimalError> {
160        self.try_floor()
161    }
162}
163
164impl fmt::Display for Decimal {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        let mut scaled_val = self.0.to_string();
167        if scaled_val.len() <= SCALE {
168            scaled_val.insert_str(0, &vec!["0"; SCALE - scaled_val.len()].join(""));
169            scaled_val.insert_str(0, "0.");
170        } else {
171            scaled_val.insert(scaled_val.len() - SCALE, '.');
172        }
173        f.write_str(&scaled_val)
174    }
175}
176
177impl<T> From<T> for Decimal
178where
179    T: Into<U128>,
180{
181    fn from(val: T) -> Self {
182        let val: U128 = val.into();
183        // Note: Some values between `u64::MAX` and `u128::MAX` can overflow...so panics here
184        Self(Self::wad().checked_mul(val.into()).unwrap())
185    }
186}
187
188impl From<Rate> for Decimal {
189    fn from(val: Rate) -> Self {
190        Self(val.to_scaled_val().unwrap())
191    }
192}
193
194impl TryAdd for Decimal {
195    fn try_add(self, rhs: Self) -> Result<Self, DecimalError> {
196        Ok(Self(
197            self.0
198                .checked_add(rhs.0)
199                .ok_or(DecimalError::MathOverflow)?,
200        ))
201    }
202}
203
204impl TrySub for Decimal {
205    fn try_sub(self, rhs: Self) -> Result<Self, DecimalError> {
206        Ok(Self(
207            self.0
208                .checked_sub(rhs.0)
209                .ok_or(DecimalError::MathOverflow)?,
210        ))
211    }
212}
213
214impl<T> TryDiv<T> for Decimal
215where
216    T: Into<U192>,
217{
218    fn try_div(self, rhs: T) -> Result<Self, DecimalError> {
219        Ok(Self(
220            self.0
221                .checked_div(rhs.into())
222                .ok_or(DecimalError::MathOverflow)?,
223        ))
224    }
225}
226
227impl TryDiv<Rate> for Decimal {
228    fn try_div(self, rhs: Rate) -> Result<Self, DecimalError> {
229        self.try_div(Self::from(rhs))
230    }
231}
232
233impl TryDiv<Decimal> for Decimal {
234    fn try_div(self, rhs: Self) -> Result<Self, DecimalError> {
235        Ok(Self(
236            self.0
237                .checked_mul(Self::wad())
238                .ok_or(DecimalError::MathOverflow)?
239                .checked_div(rhs.0)
240                .ok_or(DecimalError::MathOverflow)?,
241        ))
242    }
243}
244
245impl<T> TryMul<T> for Decimal
246where
247    T: Into<U192>,
248{
249    fn try_mul(self, rhs: T) -> Result<Self, DecimalError> {
250        Ok(Self(
251            self.0
252                .checked_mul(rhs.into())
253                .ok_or(DecimalError::MathOverflow)?,
254        ))
255    }
256}
257
258impl TryMul<Rate> for Decimal {
259    fn try_mul(self, rhs: Rate) -> Result<Self, DecimalError> {
260        self.try_mul(Self::from(rhs))
261    }
262}
263
264impl TryMul<Decimal> for Decimal {
265    fn try_mul(self, rhs: Self) -> Result<Self, DecimalError> {
266        Ok(Self(
267            self.0
268                .checked_mul(rhs.0)
269                .ok_or(DecimalError::MathOverflow)?
270                .checked_div(Self::wad())
271                .ok_or(DecimalError::MathOverflow)?,
272        ))
273    }
274}
275
276#[cfg(test)]
277mod test {
278    use super::*;
279
280    #[test]
281    fn test_scaler() {
282        assert_eq!(U192::exp10(SCALE), Decimal::wad());
283    }
284
285    #[test]
286    fn test_decimal_from_to_percent() {
287        let pct = 10; // 10%
288        let x = Decimal::from_percent(pct);
289        let pct_actual = x.to_percent().unwrap();
290
291        assert_eq!(pct as u128, pct_actual);
292    }
293}