1use std::fmt::{Display, Formatter};
2
3const MAX_I128_REPR: i128 = 0x0000_0000_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF;
4
5#[derive(Debug)]
6pub struct Unpacked {
7 pub mantissa: i128,
8 pub scale: u32,
9}
10
11impl Unpacked {
12 pub const fn lo(&self) -> u32 {
13 self.mantissa.unsigned_abs() as u32
14 }
15
16 pub const fn mid(&self) -> u32 {
17 (self.mantissa.unsigned_abs() >> 32) as u32
18 }
19
20 pub const fn hi(&self) -> u32 {
21 (self.mantissa.unsigned_abs() >> 64) as u32
22 }
23
24 pub const fn negative(&self) -> bool {
25 self.mantissa < 0
26 }
27}
28
29pub type ParseResult<'str> = Result<Unpacked, ParseError<'str>>;
30
31#[derive(Debug, PartialEq)]
32pub enum ParseError<'src> {
33 Empty,
34 ExceedsMaximumPossibleValue,
35 FractionEmpty,
36 InvalidExp(i32),
37 InvalidRadix(u32),
38 LessThanMinimumPossibleValue,
39 Underflow,
40 Unparseable(&'src [u8]),
41}
42
43impl Display for ParseError<'_> {
44 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
45 match self {
46 ParseError::Empty => write!(f, "Number is empty, must have an integer part."),
47 ParseError::ExceedsMaximumPossibleValue => {
48 write!(f, "Number exceeds maximum value that can be represented.")
49 }
50 ParseError::FractionEmpty => write!(f, "Fraction empty, consider adding a `0` after the period."),
51 ParseError::InvalidExp(exp) if *exp < 0 => write!(
52 f,
53 "Scale exceeds the maximum precision allowed: {} > {}.",
54 exp.unsigned_abs(),
55 28
56 ),
57 ParseError::InvalidExp(exp) => {
58 write!(f, "Invalid exp {exp} -- exp must be in the range -28 to 28 inclusive.")
59 }
60 ParseError::InvalidRadix(radix) => write!(
61 f,
62 "Invalid radix {radix} -- radix must be in the range 2 to 36 inclusive."
63 ),
64 ParseError::LessThanMinimumPossibleValue => {
65 write!(f, "Number less than minimum value that can be represented.")
66 }
67 ParseError::Underflow => write!(f, "Number has a high precision that can not be represented."),
68 ParseError::Unparseable(src) => {
69 write!(
70 f,
71 "Cannot parse decimal, unexpected \"{}\".",
72 core::str::from_utf8(src).unwrap()
73 )
74 }
75 }
76 }
77}
78
79pub const fn parse_decimal(src: &str, exp: i32) -> ParseResult {
80 const fn skip_us(radix: u32, is_positive: bool, mut src: &[u8], exp: i32) -> ParseResult {
81 while let [b'_', rest @ ..] = src {
82 src = rest
83 }
84 parse_bytes(radix, is_positive, src, exp)
85 }
86
87 let (is_positive, src) = parse_sign(src);
88 match src {
89 [b'0', b'b', src @ ..] => skip_us(2, is_positive, src, exp),
90 [b'0', b'o', src @ ..] => skip_us(8, is_positive, src, exp),
91 [b'0', b'x', src @ ..] => skip_us(16, is_positive, src, exp),
92 src => parse_10(is_positive, src, exp),
93 }
94}
95
96pub const fn parse_decimal_with_radix(src: &str, exp: i32, radix: u32) -> ParseResult {
98 if 2 <= radix && radix <= 36 {
99 let (is_positive, src) = parse_sign(src);
100 parse_bytes(radix, is_positive, src, exp)
101 } else {
102 Err(ParseError::InvalidRadix(radix))
103 }
104}
105
106const fn parse_sign(src: &str) -> (bool, &[u8]) {
107 let mut src = src.as_bytes();
108 if let [b'-', signed @ ..] = src {
109 src = signed;
110 while let [b' ' | b'\t' | b'\n', rest @ ..] = src {
111 src = rest;
112 }
113 (false, src)
114 } else {
115 (true, src)
116 }
117}
118
119const fn parse_bytes(radix: u32, is_positive: bool, src: &[u8], exp: i32) -> ParseResult {
120 match parse_bytes_inner(radix, src, 0) {
121 (.., Some(rest)) => Err(ParseError::Unparseable(rest)),
122 (_, 0, _) => Err(ParseError::Empty),
123 (num, ..) => to_decimal(is_positive, num, exp),
124 }
125}
126
127const fn to_decimal<'src>(is_positive: bool, mut num: i128, mut exp: i32) -> ParseResult<'src> {
129 use ParseError::*;
131
132 const POWERS_10: [i128; 29] = {
133 let mut powers_10 = [1; 29];
134 let mut i = 1;
136 while i < 29 {
137 powers_10[i] = 10 * powers_10[i - 1];
138 i += 1;
139 }
140 powers_10
141 };
142
143 if exp >= 0 {
144 if exp > 28 {
145 return Err(InvalidExp(exp));
146 }
147 if let Some(shifted) = num.checked_mul(POWERS_10[exp as usize]) {
148 num = shifted
149 }
150 exp = 0;
151 } else if exp < -28 {
152 return Err(InvalidExp(exp));
153 }
154 if num > MAX_I128_REPR {
155 return Err(if is_positive {
156 ExceedsMaximumPossibleValue
157 } else {
158 LessThanMinimumPossibleValue
159 });
160 }
161 Ok(Unpacked {
162 mantissa: if is_positive { num } else { -num },
163 scale: exp.unsigned_abs(),
164 })
165}
166
167const fn parse_10(is_positive: bool, src: &[u8], mut exp: i32) -> ParseResult {
169 let (mut num, len, mut more) = parse_bytes_inner(10, src, 0);
171 if len == 0 {
173 return Err(ParseError::Empty);
174 }
175
176 if let Some([b'.', rest @ ..]) = more {
178 let (whole_num, scale, _more) = parse_bytes_inner(10, rest, num);
179 more = _more;
180 if scale == 0 && more.is_some() {
182 return Err(ParseError::FractionEmpty);
183 }
184 num = whole_num;
185 if num > MAX_I128_REPR {
186 return Err(ParseError::Underflow);
187 }
188 exp -= scale as i32
189 }
190
191 if let Some([b'e' | b'E', rest @ ..]) = more {
193 let (rest, exp_is_positive) = if let [sign @ b'-' | sign @ b'+', signed @ ..] = rest {
194 (signed, *sign == b'+')
195 } else {
196 (rest, true)
197 };
198 let (e_part, _, _more) = parse_bytes_inner(10, rest, 0);
200 more = _more;
201 if e_part > i32::MAX as i128 {
203 return Err(ParseError::InvalidExp(i32::MAX));
204 }
205 if exp_is_positive {
206 exp += e_part as i32
207 } else {
208 exp -= e_part as i32
209 }
210 }
211
212 if let Some(rest) = more {
213 Err(ParseError::Unparseable(rest))
214 } else {
215 to_decimal(is_positive, num, exp)
216 }
217}
218
219const fn parse_bytes_inner(radix: u32, src: &[u8], mut num: i128) -> (i128, u8, Option<&[u8]>) {
223 let mut count = 0;
224 let mut next = src;
225 while let [byte, rest @ ..] = next {
226 if let Some(digit) = (*byte as char).to_digit(radix) {
227 count += 1;
228 num = num.saturating_mul(radix as i128).saturating_add(digit as i128);
229 } else if *byte != b'_' || count == 0 {
230 return (num, count, Some(next));
231 }
232 next = rest;
233 }
234 (num, count, None)
235}
236
237#[cfg(test)]
238mod test {
239 use super::*;
240 use rust_decimal::Decimal;
241
242 #[test]
243 pub fn parse_bytes_inner_full() {
244 let test = |radix, src: &str, result| {
245 assert_eq!(parse_bytes_inner(radix, src.as_bytes(), 0).0, result, "{radix}, {src}")
246 };
247
248 test(2, "111", 0b111);
249 test(2, "111", 0b111);
250 test(8, "177", 0o177);
251 test(10, "199", 199);
252 test(16, "1ff", 0x1ff);
253 test(36, "1_zzz", i128::from_str_radix("1zzz", 36).unwrap());
254
255 test(16, "7fff_ffff_ffff_ffff_ffff_ffff_ffff_fffE", i128::MAX - 1);
256 test(16, "7fff_ffff_ffff_ffff_ffff_ffff_ffff_fffF", i128::MAX);
257 test(16, "Ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff", i128::MAX);
259 }
260
261 #[test]
262 pub fn parse_bytes_inner_partial() {
263 macro_rules! test {
265 ($radix:expr, $src:expr, $num:expr; $result:tt) => {
266 assert!(
267 matches!(parse_bytes_inner($radix, $src.as_bytes(), $num), $result),
268 "{}, {}, {}",
269 $radix,
270 $src,
271 $num
272 );
273 };
274 }
275 test!(10, "01_234.567_8", 0;
277 (1234, 5, Some(b".567_8")));
278 test!(10, "567_8", 1234;
280 (12345678, 4, None));
281 }
282
283 fn parse_dec(src: &str, exp: i32) -> Result<Decimal, ParseError> {
285 let unpacked = parse_decimal(src, exp)?;
286 Ok(Decimal::from_i128_with_scale(unpacked.mantissa, unpacked.scale))
287 }
288
289 fn parse_radix_dec(src: &str, exp: i32, radix: u32) -> Result<Decimal, ParseError> {
290 let unpacked = parse_decimal_with_radix(src, exp, radix)?;
291 Ok(Decimal::from_i128_with_scale(unpacked.mantissa, unpacked.scale))
292 }
293
294 #[test]
295 pub fn parse_dec_string() {
297 let test = |src, exp, result: &str| {
298 if let Err(err) = parse_dec(src, exp) {
299 assert_eq!(err.to_string(), result);
300 } else {
301 panic!("no Err {src}, {exp}")
302 }
303 };
304 test("", 0, "Number is empty, must have an integer part.");
305 test(".1", 0, "Number is empty, must have an integer part.");
306 test("1.e2", 0, "Fraction empty, consider adding a `0` after the period.");
307 test("1abc", 0, "Cannot parse decimal, unexpected \"abc\".");
308 test("1 e1", 0, "Cannot parse decimal, unexpected \" e1\".");
309 test("1e 1", 0, "Cannot parse decimal, unexpected \" 1\".");
310 test("1e+ 1", 0, "Cannot parse decimal, unexpected \" 1\".");
311 test("1e- 1", 0, "Cannot parse decimal, unexpected \" 1\".");
312 test("1e +1", 0, "Cannot parse decimal, unexpected \" +1\".");
313 test("1e -1", 0, "Cannot parse decimal, unexpected \" -1\".");
314 test(
315 "1e-1",
316 50,
317 "Invalid exp 49 -- exp must be in the range -28 to 28 inclusive.",
318 );
319 test(
320 "1e60",
321 -1,
322 "Invalid exp 59 -- exp must be in the range -28 to 28 inclusive.",
323 );
324 test(
325 "1e+80",
326 9,
327 "Invalid exp 89 -- exp must be in the range -28 to 28 inclusive.",
328 );
329 test(
330 "1",
331 99,
332 "Invalid exp 99 -- exp must be in the range -28 to 28 inclusive.",
333 );
334 }
335
336 #[test]
337 pub fn parse_dec_other() {
338 let test = |src, exp, result| {
339 if let Err(err) = parse_dec(src, exp) {
340 assert_eq!(err, result, "{src}, {exp}")
341 } else {
342 panic!("no Err {src}, {exp}")
343 }
344 };
345 test("1e1", -50, ParseError::InvalidExp(-49));
346 test("1e-80", 1, ParseError::InvalidExp(-79));
347 test("1", -99, ParseError::InvalidExp(-99));
348 test("100", 28, ParseError::ExceedsMaximumPossibleValue);
349 test("-100", 28, ParseError::LessThanMinimumPossibleValue);
350 test(
351 "100_000_000_000_000_000_000_000_000_000",
352 -1,
353 ParseError::ExceedsMaximumPossibleValue,
354 );
355 test(
356 "-100_000_000_000_000_000_000_000_000_000",
357 -1,
358 ParseError::LessThanMinimumPossibleValue,
359 );
360 test("9.000_000_000_000_000_000_000_000_000_001", 0, ParseError::Underflow);
361 }
362
363 #[test]
364 pub fn parse_radix_dec_any() {
365 let test = |radix, src, exp, result| {
366 if let Err(err) = parse_radix_dec(src, exp, radix) {
367 assert_eq!(err, result, "{src}, {exp}")
368 } else {
369 panic!("no Err {src}, {exp}")
370 }
371 };
372 test(1, "", 0, ParseError::InvalidRadix(1));
373 test(37, "", 0, ParseError::InvalidRadix(37));
374 test(4, "12_3456", 0, ParseError::Unparseable("456".as_bytes()));
375 }
376}