pyth_lazer_protocol/
price.rs

1#[cfg(test)]
2mod tests;
3
4use {
5    crate::ExponentFactor,
6    rust_decimal::{prelude::FromPrimitive, Decimal},
7    serde::{Deserialize, Serialize},
8    std::num::NonZeroI64,
9    thiserror::Error,
10};
11
12#[derive(Debug, Error)]
13pub enum PriceError {
14    #[error("decimal parse error: {0}")]
15    DecimalParse(#[from] rust_decimal::Error),
16    #[error("price value is more precise than available exponent")]
17    TooPrecise,
18    #[error("zero price is unsupported")]
19    ZeroPriceUnsupported,
20    #[error("overflow")]
21    Overflow,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
25#[repr(transparent)]
26pub struct Price(NonZeroI64);
27
28impl Price {
29    pub fn from_integer(value: i64, exponent: i16) -> Result<Price, PriceError> {
30        let mantissa = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
31            ExponentFactor::Mul(coef) => value.checked_mul(coef).ok_or(PriceError::Overflow)?,
32            ExponentFactor::Div(coef) => value.checked_div(coef).ok_or(PriceError::Overflow)?,
33        };
34        let mantissa = NonZeroI64::new(mantissa).ok_or(PriceError::ZeroPriceUnsupported)?;
35        Ok(Self(mantissa))
36    }
37
38    pub fn parse_str(value: &str, exponent: i16) -> Result<Price, PriceError> {
39        let value: Decimal = value.parse()?;
40        let mantissa = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
41            ExponentFactor::Mul(coef) => value
42                .checked_mul(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?)
43                .ok_or(PriceError::Overflow)?,
44            ExponentFactor::Div(coef) => value
45                .checked_div(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?)
46                .ok_or(PriceError::Overflow)?,
47        };
48        if !mantissa.is_integer() {
49            return Err(PriceError::TooPrecise);
50        }
51        let mantissa: i64 = mantissa.try_into().map_err(|_| PriceError::Overflow)?;
52        let mantissa = NonZeroI64::new(mantissa).ok_or(PriceError::Overflow)?;
53        Ok(Self(mantissa))
54    }
55
56    pub const fn from_nonzero_mantissa(mantissa: NonZeroI64) -> Self {
57        Self(mantissa)
58    }
59
60    pub const fn from_mantissa(mantissa: i64) -> Result<Self, PriceError> {
61        if let Some(mantissa) = NonZeroI64::new(mantissa) {
62            Ok(Self(mantissa))
63        } else {
64            Err(PriceError::ZeroPriceUnsupported)
65        }
66    }
67
68    pub fn mantissa(self) -> NonZeroI64 {
69        self.0
70    }
71
72    pub fn mantissa_i64(self) -> i64 {
73        self.0.get()
74    }
75
76    pub fn to_f64(self, exponent: i16) -> Result<f64, PriceError> {
77        match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
78            // Mul/div is reversed for converting mantissa to value
79            ExponentFactor::Mul(coef) => Ok(self.0.get() as f64 / coef as f64),
80            ExponentFactor::Div(coef) => Ok(self.0.get() as f64 * coef as f64),
81        }
82    }
83
84    pub fn from_f64(value: f64, exponent: i16) -> Result<Self, PriceError> {
85        let value = Decimal::from_f64(value).ok_or(PriceError::Overflow)?;
86        let mantissa = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
87            ExponentFactor::Mul(coef) => value
88                .checked_mul(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?)
89                .ok_or(PriceError::Overflow)?,
90            ExponentFactor::Div(coef) => value
91                .checked_div(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?)
92                .ok_or(PriceError::Overflow)?,
93        };
94        let mantissa: i64 = mantissa.try_into().map_err(|_| PriceError::Overflow)?;
95        Ok(Self(
96            NonZeroI64::new(mantissa).ok_or(PriceError::ZeroPriceUnsupported)?,
97        ))
98    }
99
100    pub fn add_with_same_exponent(self, other: Price) -> Result<Self, PriceError> {
101        let mantissa = self
102            .0
103            .get()
104            .checked_add(other.0.get())
105            .ok_or(PriceError::Overflow)?;
106        Self::from_mantissa(mantissa).map_err(|_| PriceError::ZeroPriceUnsupported)
107    }
108
109    pub fn sub_with_same_exponent(self, other: Price) -> Result<Self, PriceError> {
110        let mantissa = self
111            .0
112            .get()
113            .checked_sub(other.0.get())
114            .ok_or(PriceError::Overflow)?;
115        Self::from_mantissa(mantissa).map_err(|_| PriceError::ZeroPriceUnsupported)
116    }
117
118    pub fn mul_integer(self, factor: i64) -> Result<Self, PriceError> {
119        let mantissa = self
120            .0
121            .get()
122            .checked_mul(factor)
123            .ok_or(PriceError::Overflow)?;
124        Self::from_mantissa(mantissa).map_err(|_| PriceError::ZeroPriceUnsupported)
125    }
126
127    pub fn div_integer(self, factor: i64) -> Result<Self, PriceError> {
128        let mantissa = self
129            .0
130            .get()
131            .checked_div(factor)
132            .ok_or(PriceError::Overflow)?;
133        Self::from_mantissa(mantissa).map_err(|_| PriceError::ZeroPriceUnsupported)
134    }
135
136    pub fn mul_decimal(self, mantissa: i64, exponent: i16) -> Result<Self, PriceError> {
137        let left_mantissa = i128::from(self.0.get());
138        let right_mantissa = i128::from(mantissa);
139
140        // multiplied_mantissas = left_mantissa * right_mantissa
141        let multiplied_mantissas = left_mantissa
142            .checked_mul(right_mantissa)
143            .ok_or(PriceError::Overflow)?;
144
145        // result_mantissa = left_mantissa * right_mantissa * 10^exponent
146        // Mul/div is reversed for multiplying 10^exponent
147        let result_mantissa = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
148            ExponentFactor::Mul(coef) => multiplied_mantissas
149                .checked_div(coef.into())
150                .ok_or(PriceError::Overflow)?,
151            ExponentFactor::Div(coef) => multiplied_mantissas
152                .checked_mul(coef.into())
153                .ok_or(PriceError::Overflow)?,
154        };
155        let result_mantissa: i64 = result_mantissa
156            .try_into()
157            .map_err(|_| PriceError::Overflow)?;
158        Self::from_mantissa(result_mantissa).map_err(|_| PriceError::ZeroPriceUnsupported)
159    }
160}