solana_maths/
decimal.rs

1//! Math for preserving precision of token amounts which are limited
2//! by the SPL Token program to be at most u64::MAX.
3//!
4//! Decimals are internally scaled by a WAD (10^18) to preserve
5//! precision up to 18 decimal places. Decimals are sized to support
6//! both serialization and precise math for the full range of
7//! unsigned 64-bit integers. The underlying representation is a
8//! u192 rather than u256 to reduce compute cost while losing
9//! support for arithmetic operations at the high end of u64 range.
10
11#![allow(clippy::assign_op_pattern)]
12#![allow(clippy::ptr_offset_with_cast)]
13#![allow(clippy::manual_range_contains)]
14use crate::{
15    MathError, Rate, TryAdd, TryDiv, TryMul, TrySub, BIPS_SCALER, HALF_WAD, PERCENT_SCALER, SCALE,
16    WAD,
17};
18use arrayref::{array_mut_ref, array_ref};
19use solana_program::program_error::ProgramError;
20use solana_program::program_pack::{Pack, Sealed};
21use std::{convert::TryFrom, fmt};
22use uint::construct_uint;
23
24// U192 with 192 bits consisting of 3 x 64-bit words
25construct_uint! {
26    pub struct U192(3);
27}
28
29/// Large decimal values, precise to 18 digits
30#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Eq, Ord)]
31pub struct Decimal(pub U192);
32
33impl Decimal {
34    /// One
35    pub fn one() -> Self {
36        Self(Self::wad())
37    }
38
39    /// Zero
40    pub fn zero() -> Self {
41        Self(U192::zero())
42    }
43
44    // OPTIMIZE: use const slice when fixed in BPF toolchain
45    fn wad() -> U192 {
46        U192::from(WAD)
47    }
48
49    // OPTIMIZE: use const slice when fixed in BPF toolchain
50    fn half_wad() -> U192 {
51        U192::from(HALF_WAD)
52    }
53
54    /// Create scaled decimal from percent value
55    pub fn from_percent(percent: u8) -> Self {
56        Self(U192::from(percent as u64 * PERCENT_SCALER))
57    }
58
59    pub fn from_bips(percent: u64) -> Self {
60        Self(U192::from(percent * BIPS_SCALER))
61    }
62    /// Return raw scaled value if it fits within u128
63    #[allow(clippy::wrong_self_convention)]
64    pub fn to_scaled_val(&self) -> Result<u128, ProgramError> {
65        Ok(u128::try_from(self.0).map_err(|_| MathError::UnableToRoundU128)?)
66    }
67
68    /// Create decimal from scaled value
69    pub fn from_scaled_val(scaled_val: u128) -> Self {
70        Self(U192::from(scaled_val))
71    }
72
73    /// Round scaled decimal to u64
74    pub fn try_round_u64(&self) -> Result<u64, ProgramError> {
75        let rounded_val = Self::half_wad()
76            .checked_add(self.0)
77            .ok_or(MathError::AddOverflow)?
78            .checked_div(Self::wad())
79            .ok_or(MathError::DividedByZero)?;
80        Ok(u64::try_from(rounded_val).map_err(|_| MathError::UnableToRoundU64)?)
81    }
82
83    /// Ceiling scaled decimal to u64
84    pub fn try_ceil_u64(&self) -> Result<u64, ProgramError> {
85        let ceil_val = Self::wad()
86            .checked_sub(U192::from(1u64))
87            .ok_or(MathError::SubUnderflow)?
88            .checked_add(self.0)
89            .ok_or(MathError::AddOverflow)?
90            .checked_div(Self::wad())
91            .ok_or(MathError::DividedByZero)?;
92        Ok(u64::try_from(ceil_val).map_err(|_| MathError::UnableToRoundU64)?)
93    }
94
95    /// Floor scaled decimal to u64
96    pub fn try_floor_u64(&self) -> Result<u64, ProgramError> {
97        let ceil_val = self
98            .0
99            .checked_div(Self::wad())
100            .ok_or(MathError::DividedByZero)?;
101        Ok(u64::try_from(ceil_val).map_err(|_| MathError::UnableToRoundU64)?)
102    }
103}
104
105impl fmt::Display for Decimal {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        let mut scaled_val = self.0.to_string();
108        if scaled_val.len() <= SCALE {
109            scaled_val.insert_str(0, &vec!["0"; SCALE - scaled_val.len()].join(""));
110            scaled_val.insert_str(0, "0.");
111        } else {
112            scaled_val.insert(scaled_val.len() - SCALE, '.');
113        }
114        f.write_str(&scaled_val)
115    }
116}
117
118impl From<u64> for Decimal {
119    fn from(val: u64) -> Self {
120        Self(Self::wad() * U192::from(val))
121    }
122}
123
124impl From<u128> for Decimal {
125    fn from(val: u128) -> Self {
126        Self(Self::wad() * U192::from(val))
127    }
128}
129
130impl From<Rate> for Decimal {
131    fn from(val: Rate) -> Self {
132        Self(U192::from(val.to_scaled_val()))
133    }
134}
135
136impl TryAdd for Decimal {
137    fn try_add(self, rhs: Self) -> Result<Self, ProgramError> {
138        Ok(Self(
139            self.0.checked_add(rhs.0).ok_or(MathError::AddOverflow)?,
140        ))
141    }
142}
143
144impl TrySub for Decimal {
145    fn try_sub(self, rhs: Self) -> Result<Self, ProgramError> {
146        Ok(Self(
147            self.0.checked_sub(rhs.0).ok_or(MathError::MulOverflow)?,
148        ))
149    }
150}
151
152impl TryDiv<u64> for Decimal {
153    fn try_div(self, rhs: u64) -> Result<Self, ProgramError> {
154        Ok(Self(
155            self.0
156                .checked_div(U192::from(rhs))
157                .ok_or(MathError::DividedByZero)?,
158        ))
159    }
160}
161
162impl TryDiv<Rate> for Decimal {
163    fn try_div(self, rhs: Rate) -> Result<Self, ProgramError> {
164        self.try_div(Self::from(rhs))
165    }
166}
167
168impl TryDiv<Decimal> for Decimal {
169    fn try_div(self, rhs: Self) -> Result<Self, ProgramError> {
170        Ok(Self(
171            self.0
172                .checked_mul(Self::wad())
173                .ok_or(MathError::MulOverflow)?
174                .checked_div(rhs.0)
175                .ok_or(MathError::DividedByZero)?,
176        ))
177    }
178}
179
180impl TryMul<u64> for Decimal {
181    fn try_mul(self, rhs: u64) -> Result<Self, ProgramError> {
182        Ok(Self(
183            self.0
184                .checked_mul(U192::from(rhs))
185                .ok_or(MathError::MulOverflow)?,
186        ))
187    }
188}
189
190impl TryMul<Rate> for Decimal {
191    fn try_mul(self, rhs: Rate) -> Result<Self, ProgramError> {
192        self.try_mul(Self::from(rhs))
193    }
194}
195
196impl TryMul<Decimal> for Decimal {
197    fn try_mul(self, rhs: Self) -> Result<Self, ProgramError> {
198        Ok(Self(
199            self.0
200                .checked_mul(rhs.0)
201                .ok_or(MathError::MulOverflow)?
202                .checked_div(Self::wad())
203                .ok_or(MathError::DividedByZero)?,
204        ))
205    }
206}
207impl Sealed for Decimal {}
208impl Pack for Decimal {
209    const LEN: usize = 16;
210    fn pack_into_slice(&self, dst: &mut [u8]) {
211        let output = array_mut_ref![dst, 0, Decimal::LEN];
212        *output = self
213            .to_scaled_val()
214            .expect("Decimal cannot be packed")
215            .to_le_bytes();
216    }
217    fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
218        let input = array_ref![src, 0, Decimal::LEN];
219        Ok(Decimal::from_scaled_val(u128::from_le_bytes(*input)))
220    }
221}
222
223#[cfg(test)]
224mod test {
225    use super::*;
226    use crate::SCALE;
227
228    #[test]
229    fn test_scaler() {
230        assert_eq!(U192::exp10(SCALE), Decimal::wad());
231    }
232}