1use cosmwasm_schema::cw_serde;
2use cosmwasm_std::{Decimal, Decimal256, Fraction, Uint128, Uint256};
3
4#[cw_serde]
5pub enum Precision {
6 SignificantFigures(u8),
7 DecimalPlaces(u8),
8}
9
10impl Default for Precision {
11 fn default() -> Self {
12 Self::DecimalPlaces(0)
13 }
14}
15
16impl Precision {
17 pub fn validate<T>(&self, other: &T) -> Option<()>
18 where
19 T: Precise + PartialEq,
20 {
21 if &other.round(self) == other {
22 Some(())
23 } else {
24 None
25 }
26 }
27}
28
29pub trait Precise {
30 fn round(&self, other: &Precision) -> Self;
31}
32
33impl Precise for Decimal {
34 fn round(&self, p: &Precision) -> Self {
35 match p {
36 Precision::SignificantFigures(sf) => {
37 let int = self.numerator();
38 let len = int.to_string().as_str().bytes().len() as u32;
39 let decimals: u32 = len - *sf as u32;
40 let pow = Uint128::from(10u128).pow(decimals);
41 let truncated = Uint128::one().mul_floor(Self::from_ratio(int, pow));
42 Self::from_ratio(truncated * pow, self.denominator())
43 }
44 Precision::DecimalPlaces(dp) => {
45 let pow = Uint128::from(10u128).pow(18 - *dp as u32);
46 let x = Self::from_ratio(self.numerator(), self.denominator() * pow);
47 Self::from_ratio(x.numerator() * pow, x.denominator())
48 }
49 }
50 }
51}
52
53impl Precise for Decimal256 {
54 fn round(&self, p: &Precision) -> Self {
55 match p {
56 Precision::SignificantFigures(sf) => {
57 let int = self.numerator();
58 let len = int.to_string().as_str().bytes().len() as u32;
59 let decimals: u32 = len - *sf as u32;
60 let pow = Uint256::from(10u128).pow(decimals);
61 let truncated = Uint256::one().mul_floor(Self::from_ratio(int, pow));
62 Self::from_ratio(truncated * pow, self.denominator())
63 }
64 Precision::DecimalPlaces(dp) => {
65 let pow = Uint256::from(10u128).pow(18 - *dp as u32);
66 let x = Self::from_ratio(self.numerator(), self.denominator() * pow);
67 Self::from_ratio(x.numerator() * pow, x.denominator())
68 }
69 }
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use std::str::FromStr;
77
78 #[test]
79 fn test_significant_figures() {
80 let p = Precision::SignificantFigures(2);
81 assert_eq!(p.validate(&Decimal::from_str("123").unwrap()), None);
82 assert_eq!(p.validate(&Decimal::from_str("12").unwrap()), Some(()));
83 assert_eq!(p.validate(&Decimal::from_str("12.3").unwrap()), None);
84 assert_eq!(p.validate(&Decimal::from_str("1.2").unwrap()), Some(()));
85 }
86
87 #[test]
88 fn test_decimal_places() {
89 let p = Precision::DecimalPlaces(2);
90 assert_eq!(p.validate(&Decimal::from_str("123").unwrap()), Some(()));
91 assert_eq!(p.validate(&Decimal::from_str("1.23").unwrap()), Some(()));
92 assert_eq!(p.validate(&Decimal::from_str("12.343").unwrap()), None);
93 assert_eq!(p.validate(&Decimal::from_str("1.2").unwrap()), Some(()));
94 }
95}