Skip to main content

rust_decimal/
str.rs

1use crate::{
2    constants::{BYTES_TO_OVERFLOW_U64, MAX_SCALE, MAX_STR_BUFFER_SIZE, OVERFLOW_U96, WILL_OVERFLOW_U64},
3    error::{tail_error, Error},
4    ops::array::{add_by_internal_flattened, add_one_internal, div_by_u32, is_all_zero, mul_by_u32},
5    Decimal,
6};
7
8use arrayvec::{ArrayString, ArrayVec};
9
10use alloc::{string::String, vec::Vec};
11use core::fmt;
12
13// impl that doesn't allocate for serialization purposes.
14pub(crate) fn to_str_internal(
15    value: &Decimal,
16    append_sign: bool,
17    precision: Option<usize>,
18) -> (ArrayString<MAX_STR_BUFFER_SIZE>, Option<usize>) {
19    // Get the scale - where we need to put the decimal point
20    let scale = value.scale() as usize;
21
22    // Convert to a string and manipulate that (neg at front, inject decimal)
23    let mut chars = ArrayVec::<_, MAX_STR_BUFFER_SIZE>::new();
24    let mut working = value.mantissa_array3();
25    while !is_all_zero(&working) {
26        let remainder = div_by_u32(&mut working, 10u32);
27        chars.push(char::from(b'0' + remainder as u8));
28    }
29    while scale > chars.len() {
30        chars.push('0');
31    }
32
33    let (prec, additional) = match precision {
34        Some(prec) => {
35            let max: usize = MAX_SCALE.into();
36            if prec > max {
37                (max, Some(prec - max))
38            } else {
39                (prec, None)
40            }
41        }
42        None => (scale, None),
43    };
44
45    let len = chars.len();
46    let whole_len = len - scale;
47    let mut rep = ArrayString::new();
48    // Append the negative sign if necessary while also keeping track of the length of an "empty" string representation
49    let empty_len = if append_sign && value.is_sign_negative() {
50        rep.push('-');
51        1
52    } else {
53        0
54    };
55    for i in 0..whole_len + prec {
56        if i == len - scale {
57            if i == 0 {
58                rep.push('0');
59            }
60            rep.push('.');
61        }
62
63        if i >= len {
64            rep.push('0');
65        } else {
66            let c = chars[len - i - 1];
67            rep.push(c);
68        }
69    }
70
71    // corner case for when we truncated everything in a low fractional
72    if rep.len() == empty_len {
73        rep.push('0');
74    }
75
76    (rep, additional)
77}
78
79pub(crate) fn fmt_scientific_notation(
80    value: &Decimal,
81    exponent_symbol: &str,
82    f: &mut fmt::Formatter<'_>,
83) -> fmt::Result {
84    #[cfg(not(feature = "std"))]
85    use alloc::string::ToString;
86
87    if value.is_zero() {
88        return f.write_str("0e0");
89    }
90
91    // Get the scale - this is the e value. With multiples of 10 this may get bigger.
92    let mut exponent = -(value.scale() as isize);
93
94    // Convert the integral to a string
95    let mut chars = Vec::new();
96    let mut working = value.mantissa_array3();
97    while !is_all_zero(&working) {
98        let remainder = div_by_u32(&mut working, 10u32);
99        chars.push(char::from(b'0' + remainder as u8));
100    }
101
102    // First of all, apply scientific notation rules. That is:
103    //  1. If non-zero digit comes first, move decimal point left so that e is a positive integer
104    //  2. If decimal point comes first, move decimal point right until after the first non-zero digit
105    // Since decimal notation naturally lends itself this way, we just need to inject the decimal
106    // point in the right place and adjust the exponent accordingly.
107
108    let len = chars.len();
109    let mut rep;
110    // We either are operating with a precision specified, or on defaults. Defaults will perform "smart"
111    // reduction of precision.
112    if let Some(precision) = f.precision() {
113        if len > 1 {
114            // If we're zero precision AND it's trailing zeros then strip them
115            if precision == 0 && chars.iter().take(len - 1).all(|c| *c == '0') {
116                rep = chars.iter().skip(len - 1).collect::<String>();
117            } else {
118                // We may still be zero precision, however we aren't trailing zeros
119                if precision > 0 {
120                    chars.insert(len - 1, '.');
121                }
122                rep = chars
123                    .iter()
124                    .rev()
125                    // Add on extra zeros according to the precision. At least one, since we added a decimal place.
126                    .chain(core::iter::repeat(&'0'))
127                    .take(if precision == 0 { 1 } else { 2 + precision })
128                    .collect::<String>();
129            }
130            exponent += (len - 1) as isize;
131        } else if precision > 0 {
132            // We have precision that we want to add
133            chars.push('.');
134            rep = chars
135                .iter()
136                .chain(core::iter::repeat(&'0'))
137                .take(2 + precision)
138                .collect::<String>();
139        } else {
140            rep = chars.iter().collect::<String>();
141        }
142    } else if len > 1 {
143        // If the number is just trailing zeros then we treat it like 0 precision
144        if chars.iter().take(len - 1).all(|c| *c == '0') {
145            rep = chars.iter().skip(len - 1).collect::<String>();
146        } else {
147            // Otherwise, we need to insert a decimal place and make it a scientific number
148            chars.insert(len - 1, '.');
149            rep = chars.iter().rev().collect::<String>();
150        }
151        exponent += (len - 1) as isize;
152    } else {
153        rep = chars.iter().collect::<String>();
154    }
155
156    rep.push_str(exponent_symbol);
157    rep.push_str(&exponent.to_string());
158    f.pad_integral(value.is_sign_positive(), "", &rep)
159}
160
161// dedicated implementation for the most common case.
162#[inline]
163pub(crate) fn parse_str_radix_10(str: &str) -> Result<Decimal, Error> {
164    let bytes = str.as_bytes();
165    if bytes.len() < BYTES_TO_OVERFLOW_U64 {
166        parse_str_radix_10_dispatch::<false, true>(bytes)
167    } else {
168        parse_str_radix_10_dispatch::<true, true>(bytes)
169    }
170}
171
172#[inline]
173pub(crate) fn parse_str_radix_10_exact(str: &str) -> Result<Decimal, Error> {
174    let bytes = str.as_bytes();
175    if bytes.len() < BYTES_TO_OVERFLOW_U64 {
176        parse_str_radix_10_dispatch::<false, false>(bytes)
177    } else {
178        parse_str_radix_10_dispatch::<true, false>(bytes)
179    }
180}
181
182#[inline]
183fn parse_str_radix_10_dispatch<const BIG: bool, const ROUND: bool>(bytes: &[u8]) -> Result<Decimal, Error> {
184    match bytes {
185        [b, rest @ ..] => byte_dispatch_u64::<false, false, false, BIG, true, ROUND>(rest, 0, 0, *b),
186        [] => tail_error("Invalid decimal: empty"),
187    }
188}
189
190#[inline]
191fn overflow_64(val: u64) -> bool {
192    val >= WILL_OVERFLOW_U64
193}
194
195#[inline]
196pub fn overflow_128(val: u128) -> bool {
197    val >= OVERFLOW_U96
198}
199
200/// Dispatch the next byte:
201///
202/// * POINT - a decimal point has been seen
203/// * NEG - we've encountered a `-` and the number is negative
204/// * HAS - a digit has been encountered (when HAS is false it's invalid)
205/// * BIG - a number that uses 96 bits instead of only 64 bits
206/// * FIRST - true if it is the first byte in the string
207#[inline]
208fn dispatch_next<const POINT: bool, const NEG: bool, const HAS: bool, const BIG: bool, const ROUND: bool>(
209    bytes: &[u8],
210    data64: u64,
211    scale: u8,
212) -> Result<Decimal, Error> {
213    if let Some((next, bytes)) = bytes.split_first() {
214        byte_dispatch_u64::<POINT, NEG, HAS, BIG, false, ROUND>(bytes, data64, scale, *next)
215    } else {
216        handle_data::<NEG, HAS>(data64 as u128, scale)
217    }
218}
219
220/// Dispatch the next non-digit byte:
221///
222/// * POINT - a decimal point has been seen
223/// * NEG - we've encountered a `-` and the number is negative
224/// * HAS - a digit has been encountered (when HAS is false it's invalid)
225/// * BIG - a number that uses 96 bits instead of only 64 bits
226/// * FIRST - true if it is the first byte in the string
227/// * ROUND - attempt to round underflow
228#[inline(never)]
229fn non_digit_dispatch_u64<
230    const POINT: bool,
231    const NEG: bool,
232    const HAS: bool,
233    const BIG: bool,
234    const FIRST: bool,
235    const ROUND: bool,
236>(
237    bytes: &[u8],
238    data64: u64,
239    scale: u8,
240    b: u8,
241) -> Result<Decimal, Error> {
242    match b {
243        b'-' if FIRST && !HAS => dispatch_next::<false, true, false, BIG, ROUND>(bytes, data64, scale),
244        b'+' if FIRST && !HAS => dispatch_next::<false, false, false, BIG, ROUND>(bytes, data64, scale),
245        b'_' if HAS => handle_separator::<POINT, NEG, BIG, ROUND>(bytes, data64, scale),
246        b => tail_invalid_digit(b),
247    }
248}
249
250#[inline]
251fn byte_dispatch_u64<
252    const POINT: bool,
253    const NEG: bool,
254    const HAS: bool,
255    const BIG: bool,
256    const FIRST: bool,
257    const ROUND: bool,
258>(
259    bytes: &[u8],
260    data64: u64,
261    scale: u8,
262    b: u8,
263) -> Result<Decimal, Error> {
264    match b {
265        b'0'..=b'9' => handle_digit_64::<POINT, NEG, BIG, ROUND>(bytes, data64, scale, b - b'0'),
266        b'.' if !POINT => handle_point::<NEG, HAS, BIG, ROUND>(bytes, data64, scale),
267        b => non_digit_dispatch_u64::<POINT, NEG, HAS, BIG, FIRST, ROUND>(bytes, data64, scale, b),
268    }
269}
270
271#[inline(never)]
272fn handle_digit_64<const POINT: bool, const NEG: bool, const BIG: bool, const ROUND: bool>(
273    bytes: &[u8],
274    data64: u64,
275    scale: u8,
276    digit: u8,
277) -> Result<Decimal, Error> {
278    // we have already validated that we cannot overflow
279    let data64 = data64 * 10 + digit as u64;
280    let scale = if POINT { scale + 1 } else { 0 };
281
282    if let Some((next, bytes)) = bytes.split_first() {
283        let next = *next;
284        if POINT && BIG && scale >= 28 {
285            if ROUND {
286                maybe_round(data64 as u128, next, scale, POINT, NEG)
287            } else {
288                Err(Error::Underflow)
289            }
290        } else if BIG && overflow_64(data64) {
291            handle_full_128::<POINT, NEG, ROUND>(data64 as u128, bytes, scale, next)
292        } else {
293            byte_dispatch_u64::<POINT, NEG, true, BIG, false, ROUND>(bytes, data64, scale, next)
294        }
295    } else {
296        let data: u128 = data64 as u128;
297
298        handle_data::<NEG, true>(data, scale)
299    }
300}
301
302#[inline(never)]
303fn handle_point<const NEG: bool, const HAS: bool, const BIG: bool, const ROUND: bool>(
304    bytes: &[u8],
305    data64: u64,
306    scale: u8,
307) -> Result<Decimal, Error> {
308    dispatch_next::<true, NEG, HAS, BIG, ROUND>(bytes, data64, scale)
309}
310
311#[inline(never)]
312fn handle_separator<const POINT: bool, const NEG: bool, const BIG: bool, const ROUND: bool>(
313    bytes: &[u8],
314    data64: u64,
315    scale: u8,
316) -> Result<Decimal, Error> {
317    dispatch_next::<POINT, NEG, true, BIG, ROUND>(bytes, data64, scale)
318}
319
320#[inline(never)]
321#[cold]
322fn tail_invalid_digit(digit: u8) -> Result<Decimal, Error> {
323    match digit {
324        b'.' => tail_error("Invalid decimal: two decimal points"),
325        b'_' => tail_error("Invalid decimal: must start lead with a number"),
326        _ => tail_error("Invalid decimal: unknown character"),
327    }
328}
329
330#[inline(never)]
331#[cold]
332fn handle_full_128<const POINT: bool, const NEG: bool, const ROUND: bool>(
333    mut data: u128,
334    bytes: &[u8],
335    scale: u8,
336    next_byte: u8,
337) -> Result<Decimal, Error> {
338    let b = next_byte;
339    match b {
340        b'0'..=b'9' => {
341            let digit = u32::from(b - b'0');
342
343            // If the data is going to overflow then we should go into recovery mode
344            let next = (data * 10) + digit as u128;
345            if overflow_128(next) {
346                if !POINT {
347                    return tail_error("Invalid decimal: overflow from too many digits");
348                }
349
350                if ROUND {
351                    maybe_round(data, next_byte, scale, POINT, NEG)
352                } else {
353                    Err(Error::Underflow)
354                }
355            } else {
356                data = next;
357                let scale = scale + POINT as u8;
358                if let Some((next, bytes)) = bytes.split_first() {
359                    let next = *next;
360                    if POINT && scale >= 28 {
361                        if ROUND {
362                            // If it is an underscore at the rounding position we require slightly different handling to look ahead another digit
363                            if next == b'_' {
364                                // Skip consecutive underscores to find the next actual character
365                                let mut remaining_bytes = bytes;
366                                let mut next_char = None;
367                                while let Some((n, rest)) = remaining_bytes.split_first() {
368                                    if *n != b'_' {
369                                        next_char = Some(*n);
370                                        break;
371                                    }
372                                    remaining_bytes = rest;
373                                }
374
375                                if let Some(ch) = next_char {
376                                    // Skip underscores and use the next character for rounding
377                                    maybe_round(data, ch, scale, POINT, NEG)
378                                } else {
379                                    handle_data::<NEG, true>(data, scale)
380                                }
381                            } else {
382                                // Otherwise, we round as usual
383                                maybe_round(data, next, scale, POINT, NEG)
384                            }
385                        } else {
386                            Err(Error::Underflow)
387                        }
388                    } else {
389                        handle_full_128::<POINT, NEG, ROUND>(data, bytes, scale, next)
390                    }
391                } else {
392                    handle_data::<NEG, true>(data, scale)
393                }
394            }
395        }
396        b'.' if !POINT => {
397            // This call won't tail?
398            if let Some((next, bytes)) = bytes.split_first() {
399                handle_full_128::<true, NEG, ROUND>(data, bytes, scale, *next)
400            } else {
401                handle_data::<NEG, true>(data, scale)
402            }
403        }
404        b'_' => {
405            if let Some((next, bytes)) = bytes.split_first() {
406                handle_full_128::<POINT, NEG, ROUND>(data, bytes, scale, *next)
407            } else {
408                handle_data::<NEG, true>(data, scale)
409            }
410        }
411        b => tail_invalid_digit(b),
412    }
413}
414
415#[inline(never)]
416#[cold]
417fn maybe_round(mut data: u128, next_byte: u8, mut scale: u8, point: bool, negative: bool) -> Result<Decimal, Error> {
418    let digit = match next_byte {
419        b'0'..=b'9' => u32::from(next_byte - b'0'),
420        b'_' => 0, // This is perhaps an error case, but keep this here for compatibility
421        b'.' if !point => 0,
422        b => return tail_invalid_digit(b),
423    };
424
425    // Round at midpoint
426    if digit >= 5 {
427        data += 1;
428
429        // If the mantissa is now overflowing, round to the next
430        // next least significant digit and discard precision
431        if overflow_128(data) {
432            if scale == 0 {
433                return tail_error("Invalid decimal: overflow from mantissa after rounding");
434            }
435            data += 4;
436            data /= 10;
437            scale -= 1;
438        }
439    }
440
441    if negative {
442        handle_data::<true, true>(data, scale)
443    } else {
444        handle_data::<false, true>(data, scale)
445    }
446}
447
448#[inline(never)]
449fn tail_no_has() -> Result<Decimal, Error> {
450    tail_error("Invalid decimal: no digits found")
451}
452
453#[inline]
454fn handle_data<const NEG: bool, const HAS: bool>(data: u128, scale: u8) -> Result<Decimal, Error> {
455    debug_assert_eq!(data >> 96, 0);
456    if !HAS {
457        tail_no_has()
458    } else {
459        Ok(Decimal::from_parts(
460            data as u32,
461            (data >> 32) as u32,
462            (data >> 64) as u32,
463            NEG,
464            scale as u32,
465        ))
466    }
467}
468
469pub(crate) fn parse_str_radix_n(str: &str, radix: u32) -> Result<Decimal, Error> {
470    if str.is_empty() {
471        return Err(Error::from("Invalid decimal: empty"));
472    }
473    if radix < 2 {
474        return Err(Error::from("Unsupported radix < 2"));
475    }
476    if radix > 36 {
477        // As per trait documentation
478        return Err(Error::from("Unsupported radix > 36"));
479    }
480
481    let mut offset = 0;
482    let mut len = str.len();
483    let bytes = str.as_bytes();
484    let mut negative = false; // assume positive
485
486    // handle the sign
487    if bytes[offset] == b'-' {
488        negative = true; // leading minus means negative
489        offset += 1;
490        len -= 1;
491    } else if bytes[offset] == b'+' {
492        // leading + allowed
493        offset += 1;
494        len -= 1;
495    }
496
497    // should now be at numeric part of the significand
498    let mut digits_before_dot: i32 = -1; // digits before '.', -1 if no '.'
499    let mut coeff = ArrayVec::<_, 96>::new(); // integer significand array
500
501    // Supporting different radix
502    let (max_n, max_alpha_lower, max_alpha_upper) = if radix <= 10 {
503        (b'0' + (radix - 1) as u8, 0, 0)
504    } else {
505        let adj = (radix - 11) as u8;
506        (b'9', adj + b'a', adj + b'A')
507    };
508
509    // Estimate the max precision. All in all, it needs to fit into 96 bits.
510    // Rather than try to estimate, I've included the constants directly in here. We could,
511    // perhaps, replace this with a formula if it's faster - though it does appear to be log2.
512    let estimated_max_precision = match radix {
513        2 => 96,
514        3 => 61,
515        4 => 48,
516        5 => 42,
517        6 => 38,
518        7 => 35,
519        8 => 32,
520        9 => 31,
521        10 => 28,
522        11 => 28,
523        12 => 27,
524        13 => 26,
525        14 => 26,
526        15 => 25,
527        16 => 24,
528        17 => 24,
529        18 => 24,
530        19 => 23,
531        20 => 23,
532        21 => 22,
533        22 => 22,
534        23 => 22,
535        24 => 21,
536        25 => 21,
537        26 => 21,
538        27 => 21,
539        28 => 20,
540        29 => 20,
541        30 => 20,
542        31 => 20,
543        32 => 20,
544        33 => 20,
545        34 => 19,
546        35 => 19,
547        36 => 19,
548        _ => return Err(Error::from("Unsupported radix")),
549    };
550
551    let mut maybe_round = false;
552    while len > 0 {
553        let b = bytes[offset];
554        match b {
555            b'0'..=b'9' => {
556                if b > max_n {
557                    return Err(Error::from("Invalid decimal: invalid character"));
558                }
559                coeff.push(u32::from(b - b'0'));
560                offset += 1;
561                len -= 1;
562
563                // If the coefficient is longer than the max, exit early
564                if coeff.len() as u32 > estimated_max_precision {
565                    maybe_round = true;
566                    break;
567                }
568            }
569            b'a'..=b'z' => {
570                if b > max_alpha_lower {
571                    return Err(Error::from("Invalid decimal: invalid character"));
572                }
573                coeff.push(u32::from(b - b'a') + 10);
574                offset += 1;
575                len -= 1;
576
577                if coeff.len() as u32 > estimated_max_precision {
578                    maybe_round = true;
579                    break;
580                }
581            }
582            b'A'..=b'Z' => {
583                if b > max_alpha_upper {
584                    return Err(Error::from("Invalid decimal: invalid character"));
585                }
586                coeff.push(u32::from(b - b'A') + 10);
587                offset += 1;
588                len -= 1;
589
590                if coeff.len() as u32 > estimated_max_precision {
591                    maybe_round = true;
592                    break;
593                }
594            }
595            b'.' => {
596                if digits_before_dot >= 0 {
597                    return Err(Error::from("Invalid decimal: two decimal points"));
598                }
599                digits_before_dot = coeff.len() as i32;
600                offset += 1;
601                len -= 1;
602            }
603            b'_' => {
604                // Must start with a number...
605                if coeff.is_empty() {
606                    return Err(Error::from("Invalid decimal: must start lead with a number"));
607                }
608                offset += 1;
609                len -= 1;
610            }
611            _ => return Err(Error::from("Invalid decimal: unknown character")),
612        }
613    }
614
615    // If we exited before the end of the string then do some rounding if necessary
616    if maybe_round && offset < bytes.len() {
617        let next_byte = bytes[offset];
618        let digit = match next_byte {
619            b'0'..=b'9' => {
620                if next_byte > max_n {
621                    return Err(Error::from("Invalid decimal: invalid character"));
622                }
623                u32::from(next_byte - b'0')
624            }
625            b'a'..=b'z' => {
626                if next_byte > max_alpha_lower {
627                    return Err(Error::from("Invalid decimal: invalid character"));
628                }
629                u32::from(next_byte - b'a') + 10
630            }
631            b'A'..=b'Z' => {
632                if next_byte > max_alpha_upper {
633                    return Err(Error::from("Invalid decimal: invalid character"));
634                }
635                u32::from(next_byte - b'A') + 10
636            }
637            b'_' => 0,
638            b'.' => {
639                // Still an error if we have a second dp
640                if digits_before_dot >= 0 {
641                    return Err(Error::from("Invalid decimal: two decimal points"));
642                }
643                0
644            }
645            _ => return Err(Error::from("Invalid decimal: unknown character")),
646        };
647
648        // Round at midpoint
649        let midpoint = if radix & 0x1 == 1 { radix / 2 } else { (radix + 1) / 2 };
650        if digit >= midpoint {
651            let mut index = coeff.len() - 1;
652            loop {
653                let new_digit = coeff[index] + 1;
654                if new_digit <= 9 {
655                    coeff[index] = new_digit;
656                    break;
657                } else {
658                    coeff[index] = 0;
659                    if index == 0 {
660                        coeff.insert(0, 1u32);
661                        digits_before_dot += 1;
662                        coeff.pop();
663                        break;
664                    }
665                }
666                index -= 1;
667            }
668        }
669    }
670
671    // here when no characters left
672    if coeff.is_empty() {
673        return Err(Error::from("Invalid decimal: no digits found"));
674    }
675
676    let mut scale = if digits_before_dot >= 0 {
677        // we had a decimal place so set the scale
678        (coeff.len() as u32) - (digits_before_dot as u32)
679    } else {
680        0
681    };
682
683    // Parse this using specified radix
684    let mut data = [0u32, 0u32, 0u32];
685    let mut tmp = [0u32, 0u32, 0u32];
686    let len = coeff.len();
687    for (i, digit) in coeff.iter().enumerate() {
688        // If the data is going to overflow then we should go into recovery mode
689        tmp[0] = data[0];
690        tmp[1] = data[1];
691        tmp[2] = data[2];
692        let overflow = mul_by_u32(&mut tmp, radix);
693        if overflow > 0 {
694            // This means that we have more data to process, that we're not sure what to do with.
695            // This may or may not be an issue - depending on whether we're past a decimal point
696            // or not.
697            if (i as i32) < digits_before_dot && i + 1 < len {
698                return Err(Error::from("Invalid decimal: overflow from too many digits"));
699            }
700
701            if *digit >= 5 {
702                let carry = add_one_internal(&mut data);
703                if carry > 0 {
704                    // Highly unlikely scenario which is more indicative of a bug
705                    return Err(Error::from("Invalid decimal: overflow when rounding"));
706                }
707            }
708            // We're also one less digit so reduce the scale
709            let diff = (len - i) as u32;
710            if diff > scale {
711                return Err(Error::from("Invalid decimal: overflow from scale mismatch"));
712            }
713            scale -= diff;
714            break;
715        } else {
716            data[0] = tmp[0];
717            data[1] = tmp[1];
718            data[2] = tmp[2];
719            let carry = add_by_internal_flattened(&mut data, *digit);
720            if carry > 0 {
721                // Highly unlikely scenario which is more indicative of a bug
722                return Err(Error::from("Invalid decimal: overflow from carry"));
723            }
724        }
725    }
726
727    Ok(Decimal::from_parts(data[0], data[1], data[2], negative, scale))
728}
729
730#[cfg(test)]
731mod test {
732    use super::*;
733    use crate::Decimal;
734    use arrayvec::ArrayString;
735    use core::{fmt::Write, str::FromStr};
736
737    #[test]
738    fn display_does_not_overflow_max_capacity() {
739        let num = Decimal::from_str("1.2").unwrap();
740        let mut buffer = ArrayString::<64>::new();
741        buffer.write_fmt(format_args!("{num:.31}")).unwrap();
742        assert_eq!("1.2000000000000000000000000000000", buffer.as_str());
743    }
744
745    #[test]
746    fn from_str_rounding_0() {
747        assert_eq!(
748            parse_str_radix_10("1.234").unwrap().unpack(),
749            Decimal::new(1234, 3).unpack()
750        );
751    }
752
753    #[test]
754    fn from_str_rounding_1() {
755        assert_eq!(
756            parse_str_radix_10("11111_11111_11111.11111_11111_11111")
757                .unwrap()
758                .unpack(),
759            Decimal::from_i128_with_scale(11_111_111_111_111_111_111_111_111_111, 14).unpack()
760        );
761    }
762
763    #[test]
764    fn from_str_rounding_2() {
765        assert_eq!(
766            parse_str_radix_10("11111_11111_11111.11111_11111_11115")
767                .unwrap()
768                .unpack(),
769            Decimal::from_i128_with_scale(11_111_111_111_111_111_111_111_111_112, 14).unpack()
770        );
771    }
772
773    #[test]
774    fn from_str_rounding_3() {
775        assert_eq!(
776            parse_str_radix_10("11111_11111_11111.11111_11111_11195")
777                .unwrap()
778                .unpack(),
779            Decimal::from_i128_with_scale(1_111_111_111_111_111_111_111_111_1120, 14).unpack() // was Decimal::from_i128_with_scale(1_111_111_111_111_111_111_111_111_112, 13)
780        );
781    }
782
783    #[test]
784    fn from_str_rounding_4() {
785        assert_eq!(
786            parse_str_radix_10("99999_99999_99999.99999_99999_99995")
787                .unwrap()
788                .unpack(),
789            Decimal::from_i128_with_scale(10_000_000_000_000_000_000_000_000_000, 13).unpack() // was Decimal::from_i128_with_scale(1_000_000_000_000_000_000_000_000_000, 12)
790        );
791    }
792
793    #[test]
794    fn from_str_no_rounding_0() {
795        assert_eq!(
796            parse_str_radix_10_exact("1.234").unwrap().unpack(),
797            Decimal::new(1234, 3).unpack()
798        );
799    }
800
801    #[test]
802    fn from_str_no_rounding_1() {
803        assert_eq!(
804            parse_str_radix_10_exact("11111_11111_11111.11111_11111_11111"),
805            Err(Error::Underflow)
806        );
807    }
808
809    #[test]
810    fn from_str_no_rounding_2() {
811        assert_eq!(
812            parse_str_radix_10_exact("11111_11111_11111.11111_11111_11115"),
813            Err(Error::Underflow)
814        );
815    }
816
817    #[test]
818    fn from_str_no_rounding_3() {
819        assert_eq!(
820            parse_str_radix_10_exact("11111_11111_11111.11111_11111_11195"),
821            Err(Error::Underflow)
822        );
823    }
824
825    #[test]
826    fn from_str_no_rounding_4() {
827        assert_eq!(
828            parse_str_radix_10_exact("99999_99999_99999.99999_99999_99995"),
829            Err(Error::Underflow)
830        );
831    }
832
833    #[test]
834    fn from_str_many_pointless_chars() {
835        assert_eq!(
836            parse_str_radix_10("00________________________________________________________________001.1")
837                .unwrap()
838                .unpack(),
839            Decimal::from_i128_with_scale(11, 1).unpack()
840        );
841    }
842
843    #[test]
844    fn from_str_leading_0s_1() {
845        assert_eq!(
846            parse_str_radix_10("00001.1").unwrap().unpack(),
847            Decimal::from_i128_with_scale(11, 1).unpack()
848        );
849    }
850
851    #[test]
852    fn from_str_leading_0s_2() {
853        assert_eq!(
854            parse_str_radix_10("00000_00000_00000_00000_00001.00001")
855                .unwrap()
856                .unpack(),
857            Decimal::from_i128_with_scale(100001, 5).unpack()
858        );
859    }
860
861    #[test]
862    fn from_str_leading_0s_3() {
863        assert_eq!(
864            parse_str_radix_10("0.00000_00000_00000_00000_00000_00100")
865                .unwrap()
866                .unpack(),
867            Decimal::from_i128_with_scale(1, 28).unpack()
868        );
869    }
870
871    #[test]
872    fn from_str_trailing_0s_1() {
873        assert_eq!(
874            parse_str_radix_10("0.00001_00000_00000").unwrap().unpack(),
875            Decimal::from_i128_with_scale(10_000_000_000, 15).unpack()
876        );
877    }
878
879    #[test]
880    fn from_str_trailing_0s_2() {
881        assert_eq!(
882            parse_str_radix_10("0.00001_00000_00000_00000_00000_00000")
883                .unwrap()
884                .unpack(),
885            Decimal::from_i128_with_scale(100_000_000_000_000_000_000_000, 28).unpack()
886        );
887    }
888
889    #[test]
890    fn from_str_overflow_1() {
891        assert_eq!(
892            parse_str_radix_10("99999_99999_99999_99999_99999_99999.99999"),
893            // The original implementation returned
894            //              Ok(10000_00000_00000_00000_00000_0000)
895            // Which is a bug!
896            Err(Error::from("Invalid decimal: overflow from too many digits"))
897        );
898    }
899
900    #[test]
901    fn from_str_overflow_2() {
902        assert!(
903            parse_str_radix_10("99999_99999_99999_99999_99999_11111.11111").is_err(),
904            // The original implementation is 'overflow from scale mismatch'
905            // but we got rid of that now
906        );
907    }
908
909    #[test]
910    fn from_str_overflow_3() {
911        assert!(
912            parse_str_radix_10("99999_99999_99999_99999_99999_99994").is_err() // We could not get into 'overflow when rounding' or 'overflow from carry'
913                                                                               // in the original implementation because the rounding logic before prevented it
914        );
915    }
916
917    #[test]
918    fn from_str_overflow_4() {
919        assert_eq!(
920            // This does not overflow, moving the decimal point 1 more step would result in
921            // 'overflow from too many digits'
922            parse_str_radix_10("99999_99999_99999_99999_99999_999.99")
923                .unwrap()
924                .unpack(),
925            Decimal::from_i128_with_scale(10_000_000_000_000_000_000_000_000_000, 0).unpack()
926        );
927    }
928
929    #[test]
930    fn from_str_mantissa_overflow_1() {
931        // reminder:
932        assert_eq!(OVERFLOW_U96, 79_228_162_514_264_337_593_543_950_336);
933        assert_eq!(
934            parse_str_radix_10("79_228_162_514_264_337_593_543_950_33.56")
935                .unwrap()
936                .unpack(),
937            Decimal::from_i128_with_scale(79_228_162_514_264_337_593_543_950_34, 0).unpack()
938        );
939        // This is a mantissa of OVERFLOW_U96 - 1 just before reaching the last digit.
940        // Previously, this would return Err("overflow from mantissa after rounding")
941        // instead of successfully rounding.
942    }
943
944    #[test]
945    fn from_str_mantissa_overflow_2() {
946        assert_eq!(
947            parse_str_radix_10("79_228_162_514_264_337_593_543_950_335.6"),
948            Err(Error::from("Invalid decimal: overflow from mantissa after rounding"))
949        );
950        // this case wants to round to 79_228_162_514_264_337_593_543_950_340.
951        // (79_228_162_514_264_337_593_543_950_336 is OVERFLOW_U96 and too large
952        // to fit in 96 bits) which is also too large for the mantissa so fails.
953    }
954
955    #[test]
956    fn from_str_mantissa_overflow_3() {
957        // this hits the other avoidable overflow case in maybe_round
958        assert_eq!(
959            parse_str_radix_10("7.92281625142643375935439503356").unwrap().unpack(),
960            Decimal::from_i128_with_scale(79_228_162_514_264_337_593_543_950_34, 27).unpack()
961        );
962    }
963
964    #[test]
965    fn from_str_mantissa_overflow_4() {
966        // Same test as above, however with underscores. This causes issues.
967        assert_eq!(
968            parse_str_radix_10("7.9_228_162_514_264_337_593_543_950_335_6")
969                .unwrap()
970                .unpack(),
971            Decimal::from_i128_with_scale(79_228_162_514_264_337_593_543_950_34, 27).unpack()
972        );
973    }
974
975    #[test]
976    fn invalid_input_1() {
977        assert_eq!(
978            parse_str_radix_10("1.0000000000000000000000000000.5"),
979            Err(Error::from("Invalid decimal: two decimal points"))
980        );
981    }
982
983    #[test]
984    fn invalid_input_2() {
985        assert_eq!(
986            parse_str_radix_10("1.0.5"),
987            Err(Error::from("Invalid decimal: two decimal points"))
988        );
989    }
990
991    #[test]
992    fn character_at_rounding_position() {
993        let tests = [
994            // digit is at the rounding position
995            (
996                "1.000_000_000_000_000_000_000_000_000_04",
997                Ok(Decimal::from_i128_with_scale(
998                    1_000_000_000_000_000_000_000_000_000_0,
999                    28,
1000                )),
1001            ),
1002            (
1003                "1.000_000_000_000_000_000_000_000_000_06",
1004                Ok(Decimal::from_i128_with_scale(
1005                    1_000_000_000_000_000_000_000_000_000_1,
1006                    28,
1007                )),
1008            ),
1009            // Decimal point is at the rounding position
1010            (
1011                "1_000_000_000_000_000_000_000_000_000_0.4",
1012                Ok(Decimal::from_i128_with_scale(
1013                    1_000_000_000_000_000_000_000_000_000_0,
1014                    0,
1015                )),
1016            ),
1017            (
1018                "1_000_000_000_000_000_000_000_000_000_0.6",
1019                Ok(Decimal::from_i128_with_scale(
1020                    1_000_000_000_000_000_000_000_000_000_1,
1021                    0,
1022                )),
1023            ),
1024            // Placeholder is at the rounding position
1025            (
1026                "1.000_000_000_000_000_000_000_000_000_0_4",
1027                Ok(Decimal::from_i128_with_scale(
1028                    1_000_000_000_000_000_000_000_000_000_0,
1029                    28,
1030                )),
1031            ),
1032            (
1033                "1.000_000_000_000_000_000_000_000_000_0_6",
1034                Ok(Decimal::from_i128_with_scale(
1035                    1_000_000_000_000_000_000_000_000_000_1,
1036                    28,
1037                )),
1038            ),
1039            // Multiple placeholders at rounding position
1040            (
1041                "1.000_000_000_000_000_000_000_000_000_0__4",
1042                Ok(Decimal::from_i128_with_scale(
1043                    1_000_000_000_000_000_000_000_000_000_0,
1044                    28,
1045                )),
1046            ),
1047            (
1048                "1.000_000_000_000_000_000_000_000_000_0__6",
1049                Ok(Decimal::from_i128_with_scale(
1050                    1_000_000_000_000_000_000_000_000_000_1,
1051                    28,
1052                )),
1053            ),
1054            (
1055                "1.234567890123456789012345678_9",
1056                Ok(Decimal::from_i128_with_scale(12345678901234567890123456789, 28)),
1057            ),
1058            (
1059                "0.234567890123456789012345678_9",
1060                Ok(Decimal::from_i128_with_scale(2345678901234567890123456789, 28)),
1061            ),
1062            (
1063                "0.1234567890123456789012345678_9",
1064                Ok(Decimal::from_i128_with_scale(1234567890123456789012345679, 28)),
1065            ),
1066            (
1067                "0.1234567890123456789012345678_4",
1068                Ok(Decimal::from_i128_with_scale(1234567890123456789012345678, 28)),
1069            ),
1070        ];
1071
1072        for (input, expected) in tests.iter() {
1073            assert_eq!(parse_str_radix_10(input), *expected, "Test input {}", input);
1074        }
1075    }
1076
1077    #[test]
1078    fn from_str_edge_cases_1() {
1079        assert_eq!(parse_str_radix_10(""), Err(Error::from("Invalid decimal: empty")));
1080    }
1081
1082    #[test]
1083    fn from_str_edge_cases_2() {
1084        assert_eq!(
1085            parse_str_radix_10("0.1."),
1086            Err(Error::from("Invalid decimal: two decimal points"))
1087        );
1088    }
1089
1090    #[test]
1091    fn from_str_edge_cases_3() {
1092        assert_eq!(
1093            parse_str_radix_10("_"),
1094            Err(Error::from("Invalid decimal: must start lead with a number"))
1095        );
1096    }
1097
1098    #[test]
1099    fn from_str_edge_cases_4() {
1100        assert_eq!(
1101            parse_str_radix_10("1?2"),
1102            Err(Error::from("Invalid decimal: unknown character"))
1103        );
1104    }
1105
1106    #[test]
1107    fn from_str_edge_cases_5() {
1108        assert_eq!(
1109            parse_str_radix_10("."),
1110            Err(Error::from("Invalid decimal: no digits found"))
1111        );
1112    }
1113
1114    #[test]
1115    fn from_str_edge_cases_6() {
1116        // Decimal::MAX + 0.99999
1117        assert_eq!(
1118            parse_str_radix_10("79_228_162_514_264_337_593_543_950_335.99999"),
1119            Err(Error::from("Invalid decimal: overflow from mantissa after rounding"))
1120        );
1121    }
1122
1123    #[test]
1124    fn to_scientific_0() {
1125        #[cfg(not(feature = "std"))]
1126        use alloc::format;
1127
1128        let dec_zero = format!("{:e}", Decimal::ZERO);
1129        let int_zero = format!("{:e}", 0_u32);
1130        assert_eq!(dec_zero, int_zero);
1131        assert_eq!(
1132            Decimal::from_scientific(&dec_zero)
1133                .unwrap_or_else(|e| panic!("Can't parse {dec_zero} into Decimal: {e:?}")),
1134            Decimal::ZERO
1135        );
1136        assert_eq!(dec_zero, "0e0");
1137    }
1138}