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