1use cosmwasm_std::{Decimal256, Uint256};
2use num_bigint::BigUint;
3use num_rational::{Ratio, Rational64};
4use num_traits::{FromBytes, ToBytes, ToPrimitive, Zero};
5use std::fmt::{Debug, Display};
6use std::str::FromStr;
7use thiserror::Error as thiserrorError;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct Fraction(Ratio<BigUint>);
11
12impl Fraction {
13 pub fn new(numerator: BigUint, denominator: BigUint) -> Result<Self, FractionError> {
14 if denominator.is_zero() {
15 return Err(FractionError::ZeroDenominator);
16 }
17
18 Ok(Self::new_raw(numerator, denominator))
19 }
20
21 pub fn new_raw(numerator: BigUint, denominator: BigUint) -> Self {
22 Self(Ratio::new_raw(numerator, denominator))
23 }
24
25 pub fn into_raw(self) -> (BigUint, BigUint) {
26 self.0.into_raw()
27 }
28
29 pub fn from_decimal_string(decimal_str: &str) -> Result<Fraction, FractionError> {
30 let dec = Decimal256::from_str(decimal_str)?;
31 Ok(dec.into())
32 }
33
34 pub fn ratio(&self) -> &Ratio<BigUint> {
35 &self.0
36 }
37
38 pub fn numerator(&self) -> &BigUint {
39 self.ratio().numer()
40 }
41
42 pub fn denominator(&self) -> &BigUint {
43 self.ratio().denom()
44 }
45
46 pub fn reduced(self) -> Self {
47 Self(self.0.reduced())
48 }
49
50 pub fn to_human_precision(self, base_precision: u8, quote_precision: u8) -> Self {
52 let ratio = Ratio::new(
53 BigUint::from(10u64.pow(base_precision as u32)),
54 BigUint::from(10u64.pow(quote_precision as u32)),
55 )
56 .reduced();
57
58 self * ratio.into()
59 }
60}
61
62#[derive(Debug, thiserrorError, PartialEq)]
63pub enum FractionError {
64 #[error("Cosmwasm Error: {0}")]
65 CosmwasmError(#[from] cosmwasm_std::StdError),
66 #[error("Numerator too large to fit in f64")]
67 NumeratorOverflow,
68 #[error("Denominator too large to fit in f64")]
69 DenominatorOverflow,
70 #[error("Denominator is zero")]
71 ZeroDenominator,
72}
73
74impl Display for Fraction {
75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76 std::fmt::Display::fmt(&self.ratio(), f)
77 }
78}
79
80impl From<Fraction> for Ratio<BigUint> {
81 fn from(value: Fraction) -> Self {
82 value.0
83 }
84}
85
86impl From<Ratio<BigUint>> for Fraction {
87 fn from(value: Ratio<BigUint>) -> Self {
88 Self(value)
89 }
90}
91
92impl std::ops::Mul for Fraction {
93 type Output = Fraction;
94
95 fn mul(self, rhs: Self) -> Self::Output {
96 let self_ratio: Ratio<BigUint> = self.into();
97 let rhs_ratio: Ratio<BigUint> = rhs.into();
98
99 (self_ratio * rhs_ratio).into()
100 }
101}
102
103impl From<Rational64> for Fraction {
104 fn from(value: Rational64) -> Self {
105 let numerator = value.numer().unsigned_abs();
106 let denominator = value.denom().unsigned_abs();
107 Fraction::new_raw(BigUint::from(numerator), BigUint::from(denominator))
109 }
110}
111
112impl From<Fraction> for Decimal256 {
113 fn from(fraction: Fraction) -> Self {
114 let mut n_bytes = fraction.numerator().to_le_bytes();
115 n_bytes.resize(32, 0);
116 let numerator = Uint256::from_le_bytes(n_bytes.try_into().unwrap());
117 let mut d_bytes = fraction.denominator().to_le_bytes();
118 d_bytes.resize(32, 0);
119 let denominator = Uint256::from_le_bytes(d_bytes.try_into().unwrap());
120 Decimal256::from_ratio(numerator, denominator)
121 }
122}
123
124impl TryFrom<Fraction> for f64 {
125 type Error = FractionError;
126
127 fn try_from(value: Fraction) -> Result<Self, Self::Error> {
128 let numerator = value
129 .numerator()
130 .to_f64()
131 .ok_or(FractionError::NumeratorOverflow)?;
132 let denominator = value
133 .denominator()
134 .to_f64()
135 .ok_or(FractionError::DenominatorOverflow)?;
136 if denominator == 0.0 {
137 return Err(FractionError::ZeroDenominator);
138 }
139 Ok(numerator / denominator)
140 }
141}
142
143impl From<Decimal256> for Fraction {
144 fn from(value: Decimal256) -> Self {
145 let numerator = BigUint::from_le_bytes(&value.atomics().to_le_bytes());
146 let denominator = BigUint::from(10u8).pow(value.decimal_places());
147 Fraction::new_raw(numerator, denominator).reduced()
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn test_fraction_to_decimal256() {
157 let fraction = Fraction::new_raw(BigUint::from(100u8), BigUint::from(200u8));
158 let decimal: Decimal256 = fraction.into();
159 assert_eq!(decimal, Decimal256::percent(50));
160
161 let fraction = Fraction::new_raw(BigUint::from(3u8), BigUint::from(4u8));
162 let decimal: Decimal256 = fraction.into();
163 assert_eq!(decimal, Decimal256::percent(75));
164 }
165
166 #[test]
167 fn test_decimal256_to_fraction() {
168 let decimal = Decimal256::percent(50);
169 let fraction: Fraction = decimal.into();
170 assert_eq!(*fraction.numerator(), BigUint::from(1u8));
171 assert_eq!(*fraction.denominator(), BigUint::from(2u8));
172
173 let decimal = Decimal256::percent(75);
174 let fraction: Fraction = decimal.into();
175 assert_eq!(*fraction.numerator(), BigUint::from(3u8));
176 assert_eq!(*fraction.denominator(), BigUint::from(4u8));
177 }
178
179 #[test]
180 fn test_rational64_to_fraction() {
181 let rational = Rational64::new(3, 4);
182 let fraction: Fraction = rational.into();
183 assert_eq!(*fraction.numerator(), BigUint::from(3u8));
184 assert_eq!(*fraction.denominator(), BigUint::from(4u8));
185
186 let rational = Rational64::new(-5, 10);
187 let fraction: Fraction = rational.into();
188 assert_eq!(*fraction.numerator(), BigUint::from(1u8));
189 assert_eq!(*fraction.denominator(), BigUint::from(2u8));
190 }
191
192 #[test]
193 fn test_fraction_to_f64() {
194 let fraction = Fraction::new_raw(BigUint::from(3u8), BigUint::from(4u8));
195 let result: f64 = fraction
196 .try_into()
197 .expect("Failed to convert Fraction to f64");
198 assert_eq!(result, 0.75);
199
200 let fraction = Fraction::new_raw(BigUint::from(1u8), BigUint::from(3u8));
201 let result: f64 = fraction
202 .try_into()
203 .expect("Failed to convert Fraction to f64");
204 assert_eq!(result, 0.3333333333333333);
205
206 let fraction = Fraction::new_raw(BigUint::from(1u8), BigUint::from(0u8));
207 let result: Result<f64, FractionError> = fraction.try_into();
208 assert!(result.is_err());
209 if let Err(e) = result {
210 assert_eq!(e, FractionError::ZeroDenominator);
211 }
212 }
213
214 #[test]
215 fn test_reduce() {
216 let price = Fraction::new_raw(BigUint::from(100u64), BigUint::from(10u64)).reduced();
217
218 assert_eq!(
219 price,
220 Fraction::new_raw(BigUint::from(10u64), BigUint::from(1u64))
221 )
222 }
223
224 #[test]
225 fn test_precision_changing() {
226 let price = Fraction::new_raw(BigUint::from(100u64), BigUint::from(1u64));
227
228 let base_precision = 2;
229 let quote_precision = 4;
230
231 assert_eq!(
232 price.to_human_precision(base_precision, quote_precision),
233 Fraction::new_raw(BigUint::from(1u64), BigUint::from(1u64))
234 );
235 }
236}