perfect_decimal/
decimal.rs1use 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}