Skip to main content

perfect_decimal/
decimal.rs

1use std::fmt::{Display, Formatter, Result as FmtResult};
2use std::ops::{Add, Div, Mul, Sub};
3use std::str::FromStr;
4
5use schemars::JsonSchema;
6use serde::{
7  de::Error as DeserializeError, ser::Error as SerializeError, Deserialize, Deserializer,
8  Serialize, Serializer,
9};
10use serde_json::Number as JsonNumber;
11
12use crate::error::{Error, Result};
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, JsonSchema)]
15pub struct SafeDecimal(#[schemars(with = "f64")] u64);
16
17impl SafeDecimal {
18  const MAX: u32 = 1_000_000_000;
19  const DECIMALS: u32 = 6;
20  const SCALE: u32 = 10u32.pow(Self::DECIMALS);
21  const MAX_VAL: u64 = Self::MAX as u64 * Self::SCALE as u64 - 1;
22
23  pub fn new(integral: u32, fractional: u32) -> Result<Self> {
24    if integral >= Self::MAX || fractional >= Self::SCALE {
25      return Err(Error::Overflow {});
26    }
27    Ok(Self(integral as u64 * Self::SCALE as u64 + fractional as u64))
28  }
29
30  pub fn integral(&self) -> u32 {
31    (self.0 / Self::SCALE as u64) as u32
32  }
33
34  pub fn fractional(&self) -> u32 {
35    (self.0 % Self::SCALE as u64) as u32
36  }
37}
38
39impl Add for SafeDecimal {
40  type Output = Result<SafeDecimal>;
41
42  fn add(self, rhs: Self) -> Self::Output {
43    Ok(Self(self.0.checked_add(rhs.0).ok_or(Error::Overflow {})?))
44  }
45}
46
47impl Sub for SafeDecimal {
48  type Output = Result<SafeDecimal>;
49
50  fn sub(self, rhs: Self) -> Self::Output {
51    Ok(Self(self.0.checked_sub(rhs.0).ok_or(Error::Overflow {})?))
52  }
53}
54
55impl Mul for SafeDecimal {
56  type Output = Result<SafeDecimal>;
57
58  fn mul(self, rhs: Self) -> Self::Output {
59    let res = self.0 as u128 * rhs.0 as u128 / Self::SCALE as u128;
60    if res > Self::MAX_VAL as u128 {
61      return Err(Error::Overflow {});
62    }
63    Ok(Self(res as u64))
64  }
65}
66
67impl Div for SafeDecimal {
68  type Output = Result<SafeDecimal>;
69
70  fn div(self, rhs: Self) -> Self::Output {
71    let res = self.0 as u128 * Self::SCALE as u128 / rhs.0 as u128;
72    if res > Self::MAX_VAL as u128 {
73      return Err(Error::Overflow {});
74    }
75    Ok(Self(res as u64))
76  }
77}
78
79impl FromStr for SafeDecimal {
80  type Err = Error;
81
82  fn from_str(s: &str) -> Result<Self> {
83    let mut parts = s.split('.');
84    let integral: u32 = parts.next().ok_or(Error::UnexpectedFormat {})?.parse()?;
85    let maybe_fractional: Option<u32> =
86      parts.next().map(|s| format!("{:0<6}", s.trim_end_matches("0")).parse()).transpose()?;
87    if parts.next().is_some() {
88      return Err(Error::UnexpectedFormat {});
89    }
90    match maybe_fractional {
91      Some(fractional) => Self::new(integral, fractional),
92      None => Self::new(integral, 0),
93    }
94  }
95}
96
97impl<'de> Deserialize<'de> for SafeDecimal {
98  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
99  where D: Deserializer<'de> {
100    String::deserialize(deserializer)?.parse().map_err(D::Error::custom)
101  }
102}
103
104impl Serialize for SafeDecimal {
105  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
106  where S: Serializer {
107    self.to_string().parse::<JsonNumber>().map_err(S::Error::custom)?.serialize(serializer)
108  }
109}
110
111impl Display for SafeDecimal {
112  fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
113    let integral = self.integral();
114    let fractional = self.fractional();
115    if fractional == 0 {
116      write!(f, "{}", integral)
117    } else {
118      let mut frac_str = format!("{:06}", fractional);
119      frac_str = frac_str.trim_end_matches('0').to_string();
120      write!(f, "{}.{}", integral, frac_str)
121    }
122  }
123}
124
125impl TryFrom<u32> for SafeDecimal {
126  type Error = Error;
127
128  fn try_from(value: u32) -> Result<Self> {
129    Self::new(value, 0)
130  }
131}
132
133impl TryFrom<u64> for SafeDecimal {
134  type Error = Error;
135
136  fn try_from(value: u64) -> Result<Self> {
137    if value > u32::MAX as u64 {
138      return Err(Error::Overflow {});
139    }
140    Self::new(value as u32, 0)
141  }
142}
143
144impl TryFrom<u128> for SafeDecimal {
145  type Error = Error;
146
147  fn try_from(value: u128) -> Result<Self> {
148    if value > u32::MAX as u128 {
149      return Err(Error::Overflow {});
150    }
151    Self::new(value as u32, 0)
152  }
153}
154
155#[cfg(test)]
156mod tests {
157  use super::*;
158
159  #[test]
160  fn test_new_valid() {
161    let decimal = SafeDecimal::new(123, 456789).unwrap();
162    assert_eq!(decimal.integral(), 123);
163    assert_eq!(decimal.fractional(), 456789);
164  }
165
166  #[test]
167  fn test_new_overflow() {
168    assert!(matches!(SafeDecimal::new(1_000_000_000, 0), Err(Error::Overflow {})));
169    assert!(matches!(SafeDecimal::new(0, 1_000_000), Err(Error::Overflow {})));
170  }
171
172  #[test]
173  fn test_add() {
174    let a = SafeDecimal::new(1, 500000).unwrap();
175    let b = SafeDecimal::new(2, 700000).unwrap();
176    let result = (a + b).unwrap();
177    assert_eq!(result.integral(), 4);
178    assert_eq!(result.fractional(), 200000);
179  }
180
181  #[test]
182  fn test_sub() {
183    let a = SafeDecimal::new(5, 300000).unwrap();
184    let b = SafeDecimal::new(2, 100000).unwrap();
185    let result = (a - b).unwrap();
186    assert_eq!(result.integral(), 3);
187    assert_eq!(result.fractional(), 200000);
188  }
189
190  #[test]
191  fn test_mul() {
192    let a = SafeDecimal::new(2, 500000).unwrap();
193    let b = SafeDecimal::new(3, 0).unwrap();
194    let result = (a * b).unwrap();
195    assert_eq!(result.integral(), 7);
196    assert_eq!(result.fractional(), 500000);
197  }
198
199  #[test]
200  fn test_div() {
201    let a = SafeDecimal::new(10, 0).unwrap();
202    let b = SafeDecimal::new(2, 0).unwrap();
203    let result = (a / b).unwrap();
204    assert_eq!(result.integral(), 5);
205    assert_eq!(result.fractional(), 0);
206  }
207
208  #[test]
209  fn test_display() {
210    let decimal = SafeDecimal::new(123, 456789).unwrap();
211    assert_eq!(format!("{}", decimal), "123.456789");
212  }
213
214  #[test]
215  fn test_from_str_valid_integer() {
216    let result = SafeDecimal::from_str("123").unwrap();
217    assert_eq!(result.to_string(), "123");
218  }
219
220  #[test]
221  fn test_from_str_valid_decimal() {
222    let result = SafeDecimal::from_str("123.45").unwrap();
223    assert_eq!(result.to_string(), "123.45");
224  }
225
226  #[test]
227  fn test_from_str_invalid_format() {
228    assert!(SafeDecimal::from_str("123.45.67").is_err());
229  }
230
231  #[test]
232  fn test_from_str_empty_string() {
233    assert!(SafeDecimal::from_str("").is_err());
234  }
235
236  #[test]
237  fn test_deserialize_valid() {
238    let json = serde_json::json!("123.45");
239    let result: SafeDecimal = serde_json::from_value(json).unwrap();
240    assert_eq!(result.to_string(), "123.45");
241  }
242
243  #[test]
244  fn test_deserialize_invalid() {
245    let json = serde_json::json!("invalid");
246    let result: Result<SafeDecimal, _> = serde_json::from_value(json);
247    assert!(result.is_err());
248  }
249
250  #[test]
251  fn test_serialize() {
252    let decimal = SafeDecimal::from_str("123.45").unwrap();
253    let json = serde_json::to_value(decimal).unwrap();
254    assert_eq!(json, serde_json::json!(123.45));
255  }
256
257  #[test]
258  fn test_try_from_u32() {
259    let decimal: SafeDecimal = 42u32.try_into().unwrap();
260    assert_eq!(decimal.integral(), 42);
261    assert_eq!(decimal.fractional(), 0);
262  }
263
264  #[test]
265  fn test_try_from_u64() {
266    let decimal: SafeDecimal = 123456789u64.try_into().unwrap();
267    assert_eq!(decimal.integral(), 123456789);
268    assert_eq!(decimal.fractional(), 0);
269  }
270
271  #[test]
272  fn test_try_from_u128_overflow() {
273    let result: Result<SafeDecimal, Error> = (u128::MAX).try_into();
274    assert!(matches!(result, Err(Error::Overflow {})));
275  }
276}