switchboard_solana/
decimal.rs

1// #![allow(unaligned_references)]
2use crate::prelude::*;
3use core::cmp::Ordering;
4use rust_decimal::prelude::{FromPrimitive, ToPrimitive};
5use rust_decimal::Decimal;
6
7#[derive(Default, Eq, PartialEq, Copy, Clone, AnchorSerialize, AnchorDeserialize)]
8pub struct BorshDecimal {
9    pub mantissa: i128,
10    pub scale: u32,
11}
12impl From<Decimal> for BorshDecimal {
13    fn from(s: Decimal) -> Self {
14        Self {
15            mantissa: s.mantissa(),
16            scale: s.scale(),
17        }
18    }
19}
20impl From<&Decimal> for BorshDecimal {
21    fn from(s: &Decimal) -> Self {
22        Self {
23            mantissa: s.mantissa(),
24            scale: s.scale(),
25        }
26    }
27}
28impl From<SwitchboardDecimal> for BorshDecimal {
29    fn from(s: SwitchboardDecimal) -> Self {
30        Self {
31            mantissa: s.mantissa,
32            scale: s.scale,
33        }
34    }
35}
36impl From<BorshDecimal> for SwitchboardDecimal {
37    fn from(val: BorshDecimal) -> Self {
38        SwitchboardDecimal {
39            mantissa: val.mantissa,
40            scale: val.scale,
41        }
42    }
43}
44impl TryInto<Decimal> for &BorshDecimal {
45    type Error = anchor_lang::error::Error;
46    fn try_into(self) -> anchor_lang::Result<Decimal> {
47        Decimal::try_from_i128_with_scale(self.mantissa, self.scale)
48            .map_err(|_| error!(SwitchboardError::DecimalConversionError))
49    }
50}
51
52impl TryInto<Decimal> for BorshDecimal {
53    type Error = anchor_lang::error::Error;
54    fn try_into(self) -> anchor_lang::Result<Decimal> {
55        Decimal::try_from_i128_with_scale(self.mantissa, self.scale)
56            .map_err(|_| error!(SwitchboardError::DecimalConversionError))
57    }
58}
59
60#[zero_copy(unsafe)]
61#[repr(packed)]
62#[derive(Default, Debug, Eq, PartialEq, AnchorDeserialize)]
63pub struct SwitchboardDecimal {
64    /// The part of a floating-point number that represents the significant digits of that number, and that is multiplied by the base, 10, raised to the power of scale to give the actual value of the number.
65    pub mantissa: i128,
66    /// The number of decimal places to move to the left to yield the actual value.
67    pub scale: u32,
68}
69
70impl SwitchboardDecimal {
71    pub fn new(mantissa: i128, scale: u32) -> SwitchboardDecimal {
72        Self { mantissa, scale }
73    }
74    pub fn from_rust_decimal(d: Decimal) -> SwitchboardDecimal {
75        Self::new(d.mantissa(), d.scale())
76    }
77    pub fn from_f64(v: f64) -> SwitchboardDecimal {
78        let dec = Decimal::from_f64(v).unwrap();
79        Self::from_rust_decimal(dec)
80    }
81    pub fn scale_to(&self, new_scale: u32) -> i128 {
82        match { self.scale }.cmp(&new_scale) {
83            std::cmp::Ordering::Greater => self
84                .mantissa
85                .checked_div(10_i128.pow(self.scale - new_scale))
86                .unwrap(),
87            std::cmp::Ordering::Less => self
88                .mantissa
89                .checked_mul(10_i128.pow(new_scale - self.scale))
90                .unwrap(),
91            std::cmp::Ordering::Equal => self.mantissa,
92        }
93    }
94    pub fn new_with_scale(&self, new_scale: u32) -> Self {
95        let mantissa = self.scale_to(new_scale);
96        SwitchboardDecimal {
97            mantissa,
98            scale: new_scale,
99        }
100    }
101}
102impl From<Decimal> for SwitchboardDecimal {
103    fn from(val: Decimal) -> Self {
104        SwitchboardDecimal::new(val.mantissa(), val.scale())
105    }
106}
107impl TryInto<Decimal> for &SwitchboardDecimal {
108    type Error = anchor_lang::error::Error;
109    fn try_into(self) -> anchor_lang::Result<Decimal> {
110        Decimal::try_from_i128_with_scale(self.mantissa, self.scale)
111            .map_err(|_| error!(SwitchboardError::DecimalConversionError))
112    }
113}
114
115impl TryInto<Decimal> for SwitchboardDecimal {
116    type Error = anchor_lang::error::Error;
117    fn try_into(self) -> anchor_lang::Result<Decimal> {
118        Decimal::try_from_i128_with_scale(self.mantissa, self.scale)
119            .map_err(|_| error!(SwitchboardError::DecimalConversionError))
120    }
121}
122
123impl Ord for SwitchboardDecimal {
124    fn cmp(&self, other: &Self) -> Ordering {
125        let s: Decimal = self.try_into().unwrap();
126        let other: Decimal = other.try_into().unwrap();
127        s.cmp(&other)
128    }
129}
130
131impl PartialOrd for SwitchboardDecimal {
132    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
133        Some(self.cmp(other))
134    }
135    fn lt(&self, other: &Self) -> bool {
136        let s: Decimal = self.try_into().unwrap();
137        let other: Decimal = other.try_into().unwrap();
138        s < other
139    }
140    fn le(&self, other: &Self) -> bool {
141        let s: Decimal = self.try_into().unwrap();
142        let other: Decimal = other.try_into().unwrap();
143        s <= other
144    }
145    fn gt(&self, other: &Self) -> bool {
146        let s: Decimal = self.try_into().unwrap();
147        let other: Decimal = other.try_into().unwrap();
148        s > other
149    }
150    fn ge(&self, other: &Self) -> bool {
151        let s: Decimal = self.try_into().unwrap();
152        let other: Decimal = other.try_into().unwrap();
153        s >= other
154    }
155}
156
157impl From<SwitchboardDecimal> for bool {
158    fn from(s: SwitchboardDecimal) -> Self {
159        let dec: Decimal = (&s).try_into().unwrap();
160        dec.round().mantissa() != 0
161    }
162}
163
164impl TryInto<u64> for SwitchboardDecimal {
165    type Error = anchor_lang::error::Error;
166    fn try_into(self) -> anchor_lang::Result<u64> {
167        let dec: Decimal = (&self).try_into().unwrap();
168        dec.to_u64()
169            .ok_or(error!(SwitchboardError::IntegerOverflowError))
170    }
171}
172
173impl TryInto<i64> for SwitchboardDecimal {
174    type Error = anchor_lang::error::Error;
175    fn try_into(self) -> anchor_lang::Result<i64> {
176        let dec: Decimal = (&self).try_into().unwrap();
177        dec.to_i64()
178            .ok_or(error!(SwitchboardError::IntegerOverflowError))
179    }
180}
181
182impl TryInto<f64> for SwitchboardDecimal {
183    type Error = anchor_lang::error::Error;
184    fn try_into(self) -> anchor_lang::Result<f64> {
185        let dec: Decimal = (&self).try_into().unwrap();
186        dec.to_f64()
187            .ok_or(error!(SwitchboardError::IntegerOverflowError))
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    #[test]
196    fn switchboard_decimal_into_rust_decimal() {
197        let swb_decimal = &SwitchboardDecimal {
198            mantissa: 12345,
199            scale: 2,
200        };
201        let decimal: Decimal = swb_decimal.try_into().unwrap();
202        assert_eq!(decimal.mantissa(), 12345);
203        assert_eq!(decimal.scale(), 2);
204    }
205
206    #[test]
207    fn empty_switchboard_decimal_is_false() {
208        let swb_decimal = SwitchboardDecimal {
209            mantissa: 0,
210            scale: 0,
211        };
212        let b: bool = swb_decimal.into();
213        assert!(!b);
214        let swb_decimal_neg = SwitchboardDecimal {
215            mantissa: -0,
216            scale: 0,
217        };
218        let b: bool = swb_decimal_neg.into();
219        assert!(!b);
220    }
221
222    #[test]
223    fn switchboard_decimal_to_u64() {
224        // 1234.5678
225        let swb_decimal = SwitchboardDecimal {
226            mantissa: 12345678,
227            scale: 4,
228        };
229        let b: u64 = swb_decimal.try_into().unwrap();
230        assert_eq!(b, 1234);
231    }
232
233    #[test]
234    fn switchboard_decimal_to_f64() {
235        // 1234.5678
236        let swb_decimal = SwitchboardDecimal {
237            mantissa: 12345678,
238            scale: 4,
239        };
240        let b: f64 = swb_decimal.try_into().unwrap();
241        assert_eq!(b, 1234.5678);
242
243        let swb_f64 = SwitchboardDecimal::from_f64(1234.5678);
244        assert_eq!(swb_decimal, swb_f64);
245    }
246}