rust_decimal_macros/
str.rs

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
96// dec!() entrypoint with radix
97pub 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
127// translate bi-directional exp to rest of lib’s scale down-only and create Decimal
128const fn to_decimal<'src>(is_positive: bool, mut num: i128, mut exp: i32) -> ParseResult<'src> {
129    // Why is scale unsigned? :-(
130    use ParseError::*;
131
132    const POWERS_10: [i128; 29] = {
133        let mut powers_10 = [1; 29];
134        // no iter in const
135        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
167// parse normal (radix 10) numbers with optional float-like .fraction and 10’s exponent
168const fn parse_10(is_positive: bool, src: &[u8], mut exp: i32) -> ParseResult {
169    // parse 1st part (upto optional . or e)
170    let (mut num, len, mut more) = parse_bytes_inner(10, src, 0);
171    // Numbers can’t be empty (before optional . or e)
172    if len == 0 {
173        return Err(ParseError::Empty);
174    }
175
176    // parse optional fraction
177    if let Some([b'.', rest @ ..]) = more {
178        let (whole_num, scale, _more) = parse_bytes_inner(10, rest, num);
179        more = _more;
180        // May only be empty if no exp
181        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    // parse optional 10’s exponent
192    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        // if this gives Some more, we’ll return that below
199        let (e_part, _, _more) = parse_bytes_inner(10, rest, 0);
200        more = _more;
201        // dummy value, more than MAX not storable
202        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
219// Can’t use `from_str_radix`, as that neither groks '_', nor allows to continue after '.' or 'e'.
220// For multi-step (see test) return: number parsed, digits count, offending rest
221// num saturates at i128::MAX, which is currently not a valid Decimal
222const 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        // must saturate at MAX
258        test(16, "Ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff", i128::MAX);
259    }
260
261    #[test]
262    pub fn parse_bytes_inner_partial() {
263        // Can only pass matcher to a macro, as normal variable would be a (useless) binding.
264        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        // Assemble floaty: number parsed, digits count, offending rest
276        test!(10, "01_234.567_8", 0;
277            (1234, 5, Some(b".567_8")));
278        // … and feed received 1234 back in, to get whole number & -exp
279        test!(10, "567_8", 1234;
280            (12345678, 4, None));
281    }
282
283    // Convert ParseResult to Decimal Result by impl From
284    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    // cases that don’t have their own Error symbol
296    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}