1use std::cmp::Ordering;
2use std::fmt::Display;
3use std::num::ParseIntError;
4use std::str::FromStr;
5
6use thiserror::Error;
7
8use crate::{Decimal, ScaledInteger};
9
10impl<I, const D: u8> Display for Decimal<I, D>
11where
12 I: ScaledInteger<D>,
13{
14 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15 let (sign, unsigned) = match self.0 < I::ZERO {
16 true => ("-", (!self.0).wrapping_add(&I::ONE)),
19 false => ("", self.0),
20 };
21 #[allow(clippy::arithmetic_side_effects)]
23 let integer = unsigned / I::SCALING_FACTOR;
24 #[allow(clippy::arithmetic_side_effects)]
26 let fractional = unsigned % I::SCALING_FACTOR;
27
28 write!(f, "{sign}{integer}.{fractional:0>decimals$}", decimals = D as usize)
29 }
30}
31
32impl<I, const D: u8> FromStr for Decimal<I, D>
33where
34 I: ScaledInteger<D>,
35{
36 type Err = ParseDecimalError<I>;
37
38 fn from_str(s: &str) -> Result<Self, Self::Err> {
39 let unsigned_s = s.strip_prefix('-').unwrap_or(s);
41
42 let (integer_s, fractional_s) = unsigned_s
44 .split_once('.')
45 .ok_or(ParseDecimalError::MissingDecimalPoint)?;
46 let integer = I::from_str(integer_s)?;
47 let fractional = I::from_str(fractional_s)?;
48
49 let scaled_integer = integer
50 .checked_mul(&I::SCALING_FACTOR)
51 .ok_or(ParseDecimalError::Overflow(integer, fractional))?;
52
53 let fractional_s_len = fractional_s.len();
54 let fractional = match fractional_s_len.cmp(&(D as usize)) {
55 Ordering::Equal => fractional,
56 Ordering::Less => {
57 #[allow(clippy::arithmetic_side_effects)]
59 let shortfall = D as usize - fractional_s_len;
60
61 fractional
63 .checked_mul(&I::pow(I::TEN, shortfall.try_into().unwrap()))
64 .unwrap()
65 }
66 Ordering::Greater => return Err(ParseDecimalError::PrecisionLoss(fractional_s.len())),
67 };
68 let unsigned = scaled_integer
69 .checked_add(&fractional)
70 .ok_or(ParseDecimalError::Overflow(integer, fractional))?;
71
72 Ok(match unsigned_s.len() == s.len() {
74 true => Decimal(unsigned),
75 false => {
76 debug_assert_eq!(unsigned_s.len().checked_add(1).unwrap(), s.len());
77
78 Decimal((!unsigned).wrapping_add(&I::ONE))
79 }
80 })
81 }
82}
83
84#[derive(Debug, PartialEq, Eq, Error)]
85pub enum ParseDecimalError<I>
86where
87 I: Display,
88{
89 #[error("Missing decimal point")]
90 MissingDecimalPoint,
91 #[error("Resultant decimal overflowed; integer={0}; fractional={1}")]
92 Overflow(I, I),
93 #[error("Failed to parse integer; err={0}")]
94 ParseInt(#[from] ParseIntError),
95 #[error("Could not parse without precision loss; decimals={0}")]
96 PrecisionLoss(usize),
97}
98
99#[cfg(test)]
100mod tests {
101 use expect_test::expect;
102 use proptest::prelude::Arbitrary;
103 use proptest::proptest;
104 use proptest::test_runner::TestRunner;
105
106 use super::*;
107 use crate::{Int64_9, Uint64_9};
108
109 #[test]
110 fn uint64_9_to_string() {
111 assert_eq!(Uint64_9::ONE.to_string(), "1.000000000");
112 assert_eq!(Uint64_9::try_from_scaled(123, 9).unwrap().to_string(), "0.000000123");
113 assert_eq!(
114 (Uint64_9::ONE + Uint64_9::try_from_scaled(123, 9).unwrap()).to_string(),
115 "1.000000123"
116 );
117 }
118
119 #[test]
120 fn uint64_9_from_str() {
121 assert_eq!("".parse::<Uint64_9>(), Err(ParseDecimalError::MissingDecimalPoint));
122 expect![[r#"
123 Err(
124 ParseInt(
125 ParseIntError {
126 kind: Empty,
127 },
128 ),
129 )
130 "#]]
131 .assert_debug_eq(&"1.".parse::<Uint64_9>());
132 assert_eq!("1.0".parse::<Uint64_9>(), Ok(Uint64_9::ONE));
133 assert_eq!("0.1".parse::<Uint64_9>(), Ok(Decimal(10u64.pow(8))));
134 assert_eq!("0.123456789".parse::<Uint64_9>(), Ok(Decimal(123456789)));
135 assert_eq!("0.012345678".parse::<Uint64_9>(), Ok(Decimal(12345678)));
136 assert_eq!("0.000000001".parse::<Uint64_9>(), Ok(Decimal(1)));
137
138 assert_eq!("0.0000000001".parse::<Uint64_9>(), Err(ParseDecimalError::PrecisionLoss(10)));
139 assert_eq!(
140 format!("{}.0", u64::MAX).parse::<Uint64_9>(),
141 Err(ParseDecimalError::Overflow(u64::MAX, 0))
142 );
143 assert_eq!(
144 format!("{}.0", u64::MAX / Uint64_9::SCALING_FACTOR).parse::<Uint64_9>(),
145 Ok(Decimal(u64::MAX / Uint64_9::SCALING_FACTOR * Uint64_9::SCALING_FACTOR))
146 );
147 assert_eq!(format!("18446744073.709551615").parse::<Uint64_9>(), Ok(Decimal::max()),);
148 assert_eq!(
149 format!("18446744073.709551616").parse::<Uint64_9>(),
150 Err(ParseDecimalError::Overflow(18446744073, 709551616)),
151 );
152 }
153
154 #[test]
155 fn int64_9_to_string() {
156 assert_eq!(Int64_9::ZERO.to_string(), "0.000000000");
157 assert_eq!(Int64_9::ONE.to_string(), "1.000000000");
158 assert_eq!(Int64_9::try_from_scaled(123, 9).unwrap().to_string(), "0.000000123");
159 assert_eq!(
160 (Int64_9::ONE + Int64_9::try_from_scaled(123, 9).unwrap()).to_string(),
161 "1.000000123"
162 );
163 assert_eq!((-Int64_9::ONE).to_string(), "-1.000000000");
164 assert_eq!((-Int64_9::try_from_scaled(123, 9).unwrap()).to_string(), "-0.000000123");
165 assert_eq!(
166 (-Int64_9::ONE + -Int64_9::try_from_scaled(123, 9).unwrap()).to_string(),
167 "-1.000000123"
168 );
169 }
170
171 #[test]
172 fn int64_9_from_str() {
173 assert_eq!("".parse::<Int64_9>(), Err(ParseDecimalError::MissingDecimalPoint));
174 expect![[r#"
175 Err(
176 ParseInt(
177 ParseIntError {
178 kind: Empty,
179 },
180 ),
181 )
182 "#]]
183 .assert_debug_eq(&"1.".parse::<Int64_9>());
184 assert_eq!("1.0".parse::<Int64_9>(), Ok(Int64_9::ONE));
185 assert_eq!("0.1".parse::<Int64_9>(), Ok(Decimal(10i64.pow(8))));
186 assert_eq!("0.123456789".parse::<Int64_9>(), Ok(Decimal(123456789)));
187 assert_eq!("0.012345678".parse::<Int64_9>(), Ok(Decimal(12345678)));
188 assert_eq!("0.000000001".parse::<Int64_9>(), Ok(Decimal(1)));
189 assert_eq!("0.0000000001".parse::<Int64_9>(), Err(ParseDecimalError::PrecisionLoss(10)));
190 assert_eq!("-1.0".parse::<Int64_9>(), Ok(-Int64_9::ONE));
191 assert_eq!("-0.1".parse::<Int64_9>(), Ok(-Decimal(10i64.pow(8))));
192 assert_eq!("-0.123456789".parse::<Int64_9>(), Ok(-Decimal(123456789)));
193 assert_eq!("-0.012345678".parse::<Int64_9>(), Ok(-Decimal(12345678)));
194 assert_eq!("-0.000000001".parse::<Int64_9>(), Ok(-Decimal(1)));
195 assert_eq!("-0.0000000001".parse::<Int64_9>(), Err(ParseDecimalError::PrecisionLoss(10)));
196 }
197
198 #[test]
202 fn uint64_9_round_trip() {
203 decimal_round_trip::<9, u64>();
204 }
205
206 #[test]
207 fn int64_9_round_trip() {
208 decimal_round_trip::<9, i64>();
209 }
210
211 #[test]
212 fn uint128_18_round_trip() {
213 decimal_round_trip::<9, u64>();
214 }
215
216 #[test]
217 fn int128_18_round_trip() {
218 decimal_round_trip::<9, i64>();
219 }
220
221 fn decimal_round_trip<const D: u8, I>()
222 where
223 I: ScaledInteger<D> + Arbitrary,
224 {
225 let mut runner = TestRunner::default();
226 let input = Decimal::arbitrary();
227
228 runner
229 .run(&input, |decimal: Decimal<I, D>| {
230 let round_trip = decimal.to_string().parse().unwrap();
231
232 assert_eq!(decimal, round_trip);
233
234 Ok(())
235 })
236 .unwrap();
237 }
238
239 #[test]
240 fn uint64_9_parse_no_panic() {
241 decimal_parse_no_panic::<9, u64>();
242 }
243
244 #[test]
245 fn int64_9_parse_no_panic() {
246 decimal_parse_no_panic::<9, i64>();
247 }
248
249 #[test]
250 fn uint128_18_parse_no_panic() {
251 decimal_parse_no_panic::<9, u64>();
252 }
253
254 #[test]
255 fn int128_18_parse_no_panic() {
256 decimal_parse_no_panic::<9, i64>();
257 }
258
259 fn decimal_parse_no_panic<const D: u8, I>()
260 where
261 I: ScaledInteger<D>,
262 {
263 proptest!(|(decimal_s: String)| {
264 let _ = decimal_s.parse::<Decimal<I, D>>();
265 });
266 }
267
268 #[test]
269 fn uint64_9_parse_numeric_no_panic() {
270 decimal_parse_numeric_no_panic::<9, u64>();
271 }
272
273 #[test]
274 fn int64_9_parse_numeric_no_panic() {
275 decimal_parse_numeric_no_panic::<9, i64>();
276 }
277
278 #[test]
279 fn uint128_18_parse_numeric_no_panic() {
280 decimal_parse_numeric_no_panic::<9, u64>();
281 }
282
283 #[test]
284 fn int128_18_parse_numeric_no_panic() {
285 decimal_parse_numeric_no_panic::<9, i64>();
286 }
287
288 fn decimal_parse_numeric_no_panic<const D: u8, I>()
289 where
290 I: ScaledInteger<D>,
291 {
292 proptest!(|(decimal_s in "[0-9]{0,24}\\.[0-9]{0,24}")| {
293 let _ = decimal_s.parse::<Decimal<I, D>>();
294 });
295 }
296}