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