Coverage Report

Created: 2026-03-28 06:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/rust_decimal-1.41.0/src/str.rs
Line
Count
Source
1
use 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
8
use arrayvec::{ArrayString, ArrayVec};
9
10
use alloc::{string::String, vec::Vec};
11
use core::fmt;
12
13
// impl that doesn't allocate for serialization purposes.
14
0
pub(crate) fn to_str_internal(
15
0
    value: &Decimal,
16
0
    append_sign: bool,
17
0
    precision: Option<usize>,
18
0
) -> (ArrayString<MAX_STR_BUFFER_SIZE>, Option<usize>) {
19
    // Get the scale - where we need to put the decimal point
20
0
    let scale = value.scale() as usize;
21
22
    // Convert to a string and manipulate that (neg at front, inject decimal)
23
0
    let mut chars = ArrayVec::<_, MAX_STR_BUFFER_SIZE>::new();
24
0
    let mut working = value.mantissa_array3();
25
0
    while !is_all_zero(&working) {
26
0
        let remainder = div_by_u32(&mut working, 10u32);
27
0
        chars.push(char::from(b'0' + remainder as u8));
28
0
    }
29
0
    while scale > chars.len() {
30
0
        chars.push('0');
31
0
    }
32
33
0
    let (prec, additional) = match precision {
34
0
        Some(prec) => {
35
0
            let max: usize = MAX_SCALE.into();
36
0
            if prec > max {
37
0
                (max, Some(prec - max))
38
            } else {
39
0
                (prec, None)
40
            }
41
        }
42
0
        None => (scale, None),
43
    };
44
45
0
    let len = chars.len();
46
0
    let whole_len = len - scale;
47
0
    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
0
    let empty_len = if append_sign && value.is_sign_negative() {
50
0
        rep.push('-');
51
0
        1
52
    } else {
53
0
        0
54
    };
55
0
    for i in 0..whole_len + prec {
56
0
        if i == len - scale {
57
0
            if i == 0 {
58
0
                rep.push('0');
59
0
            }
60
0
            rep.push('.');
61
0
        }
62
63
0
        if i >= len {
64
0
            rep.push('0');
65
0
        } else {
66
0
            let c = chars[len - i - 1];
67
0
            rep.push(c);
68
0
        }
69
    }
70
71
    // corner case for when we truncated everything in a low fractional
72
0
    if rep.len() == empty_len {
73
0
        rep.push('0');
74
0
    }
75
76
0
    (rep, additional)
77
0
}
78
79
0
pub(crate) fn fmt_scientific_notation(
80
0
    value: &Decimal,
81
0
    exponent_symbol: &str,
82
0
    f: &mut fmt::Formatter<'_>,
83
0
) -> fmt::Result {
84
    #[cfg(not(feature = "std"))]
85
    use alloc::string::ToString;
86
87
0
    if value.is_zero() {
88
0
        return f.write_str("0e0");
89
0
    }
90
91
    // Get the scale - this is the e value. With multiples of 10 this may get bigger.
92
0
    let mut exponent = -(value.scale() as isize);
93
94
    // Convert the integral to a string
95
0
    let mut chars = Vec::new();
96
0
    let mut working = value.mantissa_array3();
97
0
    while !is_all_zero(&working) {
98
0
        let remainder = div_by_u32(&mut working, 10u32);
99
0
        chars.push(char::from(b'0' + remainder as u8));
100
0
    }
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
0
    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
0
    if let Some(precision) = f.precision() {
113
0
        if len > 1 {
114
            // If we're zero precision AND it's trailing zeros then strip them
115
0
            if precision == 0 && chars.iter().take(len - 1).all(|c| *c == '0') {
116
0
                rep = chars.iter().skip(len - 1).collect::<String>();
117
0
            } else {
118
                // We may still be zero precision, however we aren't trailing zeros
119
0
                if precision > 0 {
120
0
                    chars.insert(len - 1, '.');
121
0
                }
122
0
                rep = chars
123
0
                    .iter()
124
0
                    .rev()
125
                    // Add on extra zeros according to the precision. At least one, since we added a decimal place.
126
0
                    .chain(core::iter::repeat(&'0'))
127
0
                    .take(if precision == 0 { 1 } else { 2 + precision })
128
0
                    .collect::<String>();
129
            }
130
0
            exponent += (len - 1) as isize;
131
0
        } else if precision > 0 {
132
0
            // We have precision that we want to add
133
0
            chars.push('.');
134
0
            rep = chars
135
0
                .iter()
136
0
                .chain(core::iter::repeat(&'0'))
137
0
                .take(2 + precision)
138
0
                .collect::<String>();
139
0
        } else {
140
0
            rep = chars.iter().collect::<String>();
141
0
        }
142
0
    } else if len > 1 {
143
        // If the number is just trailing zeros then we treat it like 0 precision
144
0
        if chars.iter().take(len - 1).all(|c| *c == '0') {
145
0
            rep = chars.iter().skip(len - 1).collect::<String>();
146
0
        } else {
147
0
            // Otherwise, we need to insert a decimal place and make it a scientific number
148
0
            chars.insert(len - 1, '.');
149
0
            rep = chars.iter().rev().collect::<String>();
150
0
        }
151
0
        exponent += (len - 1) as isize;
152
0
    } else {
153
0
        rep = chars.iter().collect::<String>();
154
0
    }
155
156
0
    rep.push_str(exponent_symbol);
157
0
    rep.push_str(&exponent.to_string());
158
0
    f.pad_integral(value.is_sign_positive(), "", &rep)
159
0
}
160
161
// dedicated implementation for the most common case.
162
#[inline]
163
0
pub(crate) fn parse_str_radix_10(str: &str) -> Result<Decimal, Error> {
164
0
    let bytes = str.as_bytes();
165
0
    if bytes.len() < BYTES_TO_OVERFLOW_U64 {
166
0
        parse_str_radix_10_dispatch::<false, true>(bytes)
167
    } else {
168
0
        parse_str_radix_10_dispatch::<true, true>(bytes)
169
    }
170
0
}
171
172
#[inline]
173
0
pub(crate) fn parse_str_radix_10_exact(str: &str) -> Result<Decimal, Error> {
174
0
    let bytes = str.as_bytes();
175
0
    if bytes.len() < BYTES_TO_OVERFLOW_U64 {
176
0
        parse_str_radix_10_dispatch::<false, false>(bytes)
177
    } else {
178
0
        parse_str_radix_10_dispatch::<true, false>(bytes)
179
    }
180
0
}
181
182
#[inline]
183
0
fn parse_str_radix_10_dispatch<const BIG: bool, const ROUND: bool>(bytes: &[u8]) -> Result<Decimal, Error> {
184
0
    match bytes {
185
0
        [b, rest @ ..] => byte_dispatch_u64::<false, false, false, BIG, true, ROUND>(rest, 0, 0, *b),
186
0
        [] => tail_error("Invalid decimal: empty"),
187
    }
188
0
}
Unexecuted instantiation: rust_decimal::str::parse_str_radix_10_dispatch::<false, false>
Unexecuted instantiation: rust_decimal::str::parse_str_radix_10_dispatch::<false, true>
Unexecuted instantiation: rust_decimal::str::parse_str_radix_10_dispatch::<true, true>
Unexecuted instantiation: rust_decimal::str::parse_str_radix_10_dispatch::<true, false>
189
190
#[inline]
191
0
fn overflow_64(val: u64) -> bool {
192
0
    val >= WILL_OVERFLOW_U64
193
0
}
194
195
#[inline]
196
0
pub fn overflow_128(val: u128) -> bool {
197
0
    val >= OVERFLOW_U96
198
0
}
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]
208
0
fn dispatch_next<const POINT: bool, const NEG: bool, const HAS: bool, const BIG: bool, const ROUND: bool>(
209
0
    bytes: &[u8],
210
0
    data64: u64,
211
0
    scale: u8,
212
0
) -> Result<Decimal, Error> {
213
0
    if let Some((next, bytes)) = bytes.split_first() {
214
0
        byte_dispatch_u64::<POINT, NEG, HAS, BIG, false, ROUND>(bytes, data64, scale, *next)
215
    } else {
216
0
        handle_data::<NEG, HAS>(data64 as u128, scale)
217
    }
218
0
}
Unexecuted instantiation: rust_decimal::str::dispatch_next::<false, false, false, false, false>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<false, false, false, false, true>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<false, false, false, true, true>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<false, false, false, true, false>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<false, false, true, false, false>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<false, false, true, false, true>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<false, false, true, true, false>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<false, false, true, true, true>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<false, true, false, false, false>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<false, true, false, false, true>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<false, true, false, true, false>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<false, true, false, true, true>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<false, true, true, false, false>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<false, true, true, false, true>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<false, true, true, true, false>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<false, true, true, true, true>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<true, true, true, true, true>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<true, true, true, true, false>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<true, true, true, false, false>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<true, true, true, false, true>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<true, true, false, true, true>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<true, true, false, true, false>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<true, true, false, false, true>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<true, true, false, false, false>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<true, false, true, true, true>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<true, false, true, true, false>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<true, false, true, false, true>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<true, false, true, false, false>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<true, false, false, true, true>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<true, false, false, true, false>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<true, false, false, false, true>
Unexecuted instantiation: rust_decimal::str::dispatch_next::<true, false, false, false, false>
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)]
229
0
fn non_digit_dispatch_u64<
230
0
    const POINT: bool,
231
0
    const NEG: bool,
232
0
    const HAS: bool,
233
0
    const BIG: bool,
234
0
    const FIRST: bool,
235
0
    const ROUND: bool,
236
0
>(
237
0
    bytes: &[u8],
238
0
    data64: u64,
239
0
    scale: u8,
240
0
    b: u8,
241
0
) -> Result<Decimal, Error> {
242
0
    match b {
243
0
        b'-' if FIRST && !HAS => dispatch_next::<false, true, false, BIG, ROUND>(bytes, data64, scale),
244
0
        b'+' if FIRST && !HAS => dispatch_next::<false, false, false, BIG, ROUND>(bytes, data64, scale),
245
0
        b'_' if HAS => handle_separator::<POINT, NEG, BIG, ROUND>(bytes, data64, scale),
246
0
        b => tail_invalid_digit(b),
247
    }
248
0
}
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<false, false, false, false, false, false>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<false, false, false, false, false, true>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<false, false, false, false, true, true>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<false, false, false, false, true, false>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<false, false, false, true, true, true>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<false, false, false, true, true, false>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<false, false, false, true, false, true>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<false, false, false, true, false, false>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<false, false, true, true, false, true>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<false, false, true, true, false, false>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<false, false, true, false, false, true>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<false, false, true, false, false, false>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<false, true, true, true, false, true>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<false, true, true, true, false, false>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<false, true, true, false, false, true>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<false, true, true, false, false, false>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<false, true, false, true, false, true>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<false, true, false, true, false, false>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<false, true, false, false, false, true>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<false, true, false, false, false, false>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<true, true, true, true, false, false>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<true, true, true, true, false, true>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<true, true, true, false, false, false>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<true, true, true, false, false, true>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<true, true, false, false, false, false>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<true, true, false, false, false, true>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<true, true, false, true, false, false>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<true, true, false, true, false, true>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<true, false, false, false, false, false>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<true, false, false, false, false, true>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<true, false, false, true, false, false>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<true, false, false, true, false, true>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<true, false, true, false, false, false>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<true, false, true, false, false, true>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<true, false, true, true, false, false>
Unexecuted instantiation: rust_decimal::str::non_digit_dispatch_u64::<true, false, true, true, false, true>
249
250
#[inline]
251
0
fn byte_dispatch_u64<
252
0
    const POINT: bool,
253
0
    const NEG: bool,
254
0
    const HAS: bool,
255
0
    const BIG: bool,
256
0
    const FIRST: bool,
257
0
    const ROUND: bool,
258
0
>(
259
0
    bytes: &[u8],
260
0
    data64: u64,
261
0
    scale: u8,
262
0
    b: u8,
263
0
) -> Result<Decimal, Error> {
264
0
    match b {
265
0
        b'0'..=b'9' => handle_digit_64::<POINT, NEG, BIG, ROUND>(bytes, data64, scale, b - b'0'),
266
0
        b'.' if !POINT => handle_point::<NEG, HAS, BIG, ROUND>(bytes, data64, scale),
267
0
        b => non_digit_dispatch_u64::<POINT, NEG, HAS, BIG, FIRST, ROUND>(bytes, data64, scale, b),
268
    }
269
0
}
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<false, false, false, false, false, false>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<false, false, false, false, false, true>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<false, false, false, false, true, true>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<false, false, false, false, true, false>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<false, false, false, true, true, true>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<false, false, false, true, true, false>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<false, false, false, true, false, true>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<false, false, false, true, false, false>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<false, false, true, true, false, true>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<false, false, true, true, false, false>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<false, false, true, false, false, true>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<false, false, true, false, false, false>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<false, true, false, false, false, false>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<false, true, false, false, false, true>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<false, true, false, true, false, false>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<false, true, false, true, false, true>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<false, true, true, false, false, false>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<false, true, true, false, false, true>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<false, true, true, true, false, false>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<false, true, true, true, false, true>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<true, true, true, true, false, false>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<true, true, true, true, false, true>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<true, true, true, false, false, false>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<true, true, true, false, false, true>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<true, true, false, false, false, false>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<true, true, false, false, false, true>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<true, true, false, true, false, false>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<true, true, false, true, false, true>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<true, false, true, true, false, true>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<true, false, true, true, false, false>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<true, false, true, false, false, true>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<true, false, true, false, false, false>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<true, false, false, true, false, true>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<true, false, false, true, false, false>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<true, false, false, false, false, true>
Unexecuted instantiation: rust_decimal::str::byte_dispatch_u64::<true, false, false, false, false, false>
270
271
#[inline(never)]
272
0
fn handle_digit_64<const POINT: bool, const NEG: bool, const BIG: bool, const ROUND: bool>(
273
0
    bytes: &[u8],
274
0
    data64: u64,
275
0
    scale: u8,
276
0
    digit: u8,
277
0
) -> Result<Decimal, Error> {
278
    // we have already validated that we cannot overflow
279
0
    let data64 = data64 * 10 + digit as u64;
280
0
    let scale = if POINT { scale + 1 } else { 0 };
281
282
0
    if let Some((next, bytes)) = bytes.split_first() {
283
0
        let next = *next;
284
0
        if POINT && BIG && scale >= 28 {
285
0
            if ROUND {
286
0
                maybe_round(data64 as u128, next, scale, POINT, NEG)
287
            } else {
288
0
                Err(Error::Underflow)
289
            }
290
0
        } else if BIG && overflow_64(data64) {
291
0
            handle_full_128::<POINT, NEG, ROUND>(data64 as u128, bytes, scale, next)
292
        } else {
293
0
            byte_dispatch_u64::<POINT, NEG, true, BIG, false, ROUND>(bytes, data64, scale, next)
294
        }
295
    } else {
296
0
        let data: u128 = data64 as u128;
297
298
0
        handle_data::<NEG, true>(data, scale)
299
    }
300
0
}
Unexecuted instantiation: rust_decimal::str::handle_digit_64::<false, false, false, false>
Unexecuted instantiation: rust_decimal::str::handle_digit_64::<false, false, false, true>
Unexecuted instantiation: rust_decimal::str::handle_digit_64::<false, false, true, true>
Unexecuted instantiation: rust_decimal::str::handle_digit_64::<false, false, true, false>
Unexecuted instantiation: rust_decimal::str::handle_digit_64::<false, true, false, false>
Unexecuted instantiation: rust_decimal::str::handle_digit_64::<false, true, false, true>
Unexecuted instantiation: rust_decimal::str::handle_digit_64::<false, true, true, false>
Unexecuted instantiation: rust_decimal::str::handle_digit_64::<false, true, true, true>
Unexecuted instantiation: rust_decimal::str::handle_digit_64::<true, true, true, true>
Unexecuted instantiation: rust_decimal::str::handle_digit_64::<true, true, true, false>
Unexecuted instantiation: rust_decimal::str::handle_digit_64::<true, true, false, false>
Unexecuted instantiation: rust_decimal::str::handle_digit_64::<true, true, false, true>
Unexecuted instantiation: rust_decimal::str::handle_digit_64::<true, false, true, true>
Unexecuted instantiation: rust_decimal::str::handle_digit_64::<true, false, true, false>
Unexecuted instantiation: rust_decimal::str::handle_digit_64::<true, false, false, true>
Unexecuted instantiation: rust_decimal::str::handle_digit_64::<true, false, false, false>
301
302
#[inline(never)]
303
0
fn handle_point<const NEG: bool, const HAS: bool, const BIG: bool, const ROUND: bool>(
304
0
    bytes: &[u8],
305
0
    data64: u64,
306
0
    scale: u8,
307
0
) -> Result<Decimal, Error> {
308
0
    dispatch_next::<true, NEG, HAS, BIG, ROUND>(bytes, data64, scale)
309
0
}
Unexecuted instantiation: rust_decimal::str::handle_point::<false, false, false, false>
Unexecuted instantiation: rust_decimal::str::handle_point::<false, false, false, true>
Unexecuted instantiation: rust_decimal::str::handle_point::<false, false, true, false>
Unexecuted instantiation: rust_decimal::str::handle_point::<false, false, true, true>
Unexecuted instantiation: rust_decimal::str::handle_point::<false, true, false, false>
Unexecuted instantiation: rust_decimal::str::handle_point::<false, true, false, true>
Unexecuted instantiation: rust_decimal::str::handle_point::<false, true, true, false>
Unexecuted instantiation: rust_decimal::str::handle_point::<false, true, true, true>
Unexecuted instantiation: rust_decimal::str::handle_point::<true, true, true, true>
Unexecuted instantiation: rust_decimal::str::handle_point::<true, true, true, false>
Unexecuted instantiation: rust_decimal::str::handle_point::<true, true, false, true>
Unexecuted instantiation: rust_decimal::str::handle_point::<true, true, false, false>
Unexecuted instantiation: rust_decimal::str::handle_point::<true, false, true, true>
Unexecuted instantiation: rust_decimal::str::handle_point::<true, false, true, false>
Unexecuted instantiation: rust_decimal::str::handle_point::<true, false, false, true>
Unexecuted instantiation: rust_decimal::str::handle_point::<true, false, false, false>
310
311
#[inline(never)]
312
0
fn handle_separator<const POINT: bool, const NEG: bool, const BIG: bool, const ROUND: bool>(
313
0
    bytes: &[u8],
314
0
    data64: u64,
315
0
    scale: u8,
316
0
) -> Result<Decimal, Error> {
317
0
    dispatch_next::<POINT, NEG, true, BIG, ROUND>(bytes, data64, scale)
318
0
}
Unexecuted instantiation: rust_decimal::str::handle_separator::<false, false, false, false>
Unexecuted instantiation: rust_decimal::str::handle_separator::<false, false, false, true>
Unexecuted instantiation: rust_decimal::str::handle_separator::<false, false, true, true>
Unexecuted instantiation: rust_decimal::str::handle_separator::<false, false, true, false>
Unexecuted instantiation: rust_decimal::str::handle_separator::<false, true, false, false>
Unexecuted instantiation: rust_decimal::str::handle_separator::<false, true, false, true>
Unexecuted instantiation: rust_decimal::str::handle_separator::<false, true, true, false>
Unexecuted instantiation: rust_decimal::str::handle_separator::<false, true, true, true>
Unexecuted instantiation: rust_decimal::str::handle_separator::<true, true, true, true>
Unexecuted instantiation: rust_decimal::str::handle_separator::<true, true, true, false>
Unexecuted instantiation: rust_decimal::str::handle_separator::<true, true, false, false>
Unexecuted instantiation: rust_decimal::str::handle_separator::<true, true, false, true>
Unexecuted instantiation: rust_decimal::str::handle_separator::<true, false, true, true>
Unexecuted instantiation: rust_decimal::str::handle_separator::<true, false, true, false>
Unexecuted instantiation: rust_decimal::str::handle_separator::<true, false, false, true>
Unexecuted instantiation: rust_decimal::str::handle_separator::<true, false, false, false>
319
320
#[inline(never)]
321
#[cold]
322
0
fn tail_invalid_digit(digit: u8) -> Result<Decimal, Error> {
323
0
    match digit {
324
0
        b'.' => tail_error("Invalid decimal: two decimal points"),
325
0
        b'_' => tail_error("Invalid decimal: must start lead with a number"),
326
0
        _ => tail_error("Invalid decimal: unknown character"),
327
    }
328
0
}
329
330
#[inline(never)]
331
#[cold]
332
0
fn handle_full_128<const POINT: bool, const NEG: bool, const ROUND: bool>(
333
0
    mut data: u128,
334
0
    bytes: &[u8],
335
0
    scale: u8,
336
0
    next_byte: u8,
337
0
) -> Result<Decimal, Error> {
338
0
    let b = next_byte;
339
0
    match b {
340
0
        b'0'..=b'9' => {
341
0
            let digit = u32::from(b - b'0');
342
343
            // If the data is going to overflow then we should go into recovery mode
344
0
            let next = (data * 10) + digit as u128;
345
0
            if overflow_128(next) {
346
0
                if !POINT {
347
0
                    return tail_error("Invalid decimal: overflow from too many digits");
348
0
                }
349
350
0
                if ROUND {
351
0
                    maybe_round(data, next_byte, scale, POINT, NEG)
352
                } else {
353
0
                    Err(Error::Underflow)
354
                }
355
            } else {
356
0
                data = next;
357
0
                let scale = scale + POINT as u8;
358
0
                if let Some((next, bytes)) = bytes.split_first() {
359
0
                    let next = *next;
360
0
                    if POINT && scale >= 28 {
361
0
                        if ROUND {
362
                            // If it is an underscore at the rounding position we require slightly different handling to look ahead another digit
363
0
                            if next == b'_' {
364
                                // Skip consecutive underscores to find the next actual character
365
0
                                let mut remaining_bytes = bytes;
366
0
                                let mut next_char = None;
367
0
                                while let Some((n, rest)) = remaining_bytes.split_first() {
368
0
                                    if *n != b'_' {
369
0
                                        next_char = Some(*n);
370
0
                                        break;
371
0
                                    }
372
0
                                    remaining_bytes = rest;
373
                                }
374
375
0
                                if let Some(ch) = next_char {
376
                                    // Skip underscores and use the next character for rounding
377
0
                                    maybe_round(data, ch, scale, POINT, NEG)
378
                                } else {
379
0
                                    handle_data::<NEG, true>(data, scale)
380
                                }
381
                            } else {
382
                                // Otherwise, we round as usual
383
0
                                maybe_round(data, next, scale, POINT, NEG)
384
                            }
385
                        } else {
386
0
                            Err(Error::Underflow)
387
                        }
388
                    } else {
389
0
                        handle_full_128::<POINT, NEG, ROUND>(data, bytes, scale, next)
390
                    }
391
                } else {
392
0
                    handle_data::<NEG, true>(data, scale)
393
                }
394
            }
395
        }
396
0
        b'.' if !POINT => {
397
            // This call won't tail?
398
0
            if let Some((next, bytes)) = bytes.split_first() {
399
0
                handle_full_128::<true, NEG, ROUND>(data, bytes, scale, *next)
400
            } else {
401
0
                handle_data::<NEG, true>(data, scale)
402
            }
403
        }
404
        b'_' => {
405
0
            if let Some((next, bytes)) = bytes.split_first() {
406
0
                handle_full_128::<POINT, NEG, ROUND>(data, bytes, scale, *next)
407
            } else {
408
0
                handle_data::<NEG, true>(data, scale)
409
            }
410
        }
411
0
        b => tail_invalid_digit(b),
412
    }
413
0
}
Unexecuted instantiation: rust_decimal::str::handle_full_128::<false, false, false>
Unexecuted instantiation: rust_decimal::str::handle_full_128::<false, false, true>
Unexecuted instantiation: rust_decimal::str::handle_full_128::<false, true, false>
Unexecuted instantiation: rust_decimal::str::handle_full_128::<false, true, true>
Unexecuted instantiation: rust_decimal::str::handle_full_128::<true, true, true>
Unexecuted instantiation: rust_decimal::str::handle_full_128::<true, true, false>
Unexecuted instantiation: rust_decimal::str::handle_full_128::<true, false, true>
Unexecuted instantiation: rust_decimal::str::handle_full_128::<true, false, false>
414
415
#[inline(never)]
416
#[cold]
417
0
fn maybe_round(mut data: u128, next_byte: u8, mut scale: u8, point: bool, negative: bool) -> Result<Decimal, Error> {
418
0
    let digit = match next_byte {
419
0
        b'0'..=b'9' => u32::from(next_byte - b'0'),
420
0
        b'_' => 0, // This is perhaps an error case, but keep this here for compatibility
421
0
        b'.' if !point => 0,
422
0
        b => return tail_invalid_digit(b),
423
    };
424
425
    // Round at midpoint
426
0
    if digit >= 5 {
427
0
        data += 1;
428
429
        // If the mantissa is now overflowing, round to the next
430
        // next least significant digit and discard precision
431
0
        if overflow_128(data) {
432
0
            if scale == 0 {
433
0
                return tail_error("Invalid decimal: overflow from mantissa after rounding");
434
0
            }
435
0
            data += 4;
436
0
            data /= 10;
437
0
            scale -= 1;
438
0
        }
439
0
    }
440
441
0
    if negative {
442
0
        handle_data::<true, true>(data, scale)
443
    } else {
444
0
        handle_data::<false, true>(data, scale)
445
    }
446
0
}
447
448
#[inline(never)]
449
0
fn tail_no_has() -> Result<Decimal, Error> {
450
0
    tail_error("Invalid decimal: no digits found")
451
0
}
452
453
#[inline]
454
0
fn handle_data<const NEG: bool, const HAS: bool>(data: u128, scale: u8) -> Result<Decimal, Error> {
455
0
    debug_assert_eq!(data >> 96, 0);
456
0
    if !HAS {
457
0
        tail_no_has()
458
    } else {
459
0
        Ok(Decimal::from_parts(
460
0
            data as u32,
461
0
            (data >> 32) as u32,
462
0
            (data >> 64) as u32,
463
0
            NEG,
464
0
            scale as u32,
465
0
        ))
466
    }
467
0
}
Unexecuted instantiation: rust_decimal::str::handle_data::<false, false>
Unexecuted instantiation: rust_decimal::str::handle_data::<false, true>
Unexecuted instantiation: rust_decimal::str::handle_data::<true, true>
Unexecuted instantiation: rust_decimal::str::handle_data::<true, false>
468
469
0
pub(crate) fn parse_str_radix_n(str: &str, radix: u32) -> Result<Decimal, Error> {
470
0
    if str.is_empty() {
471
0
        return Err(Error::from("Invalid decimal: empty"));
472
0
    }
473
0
    if radix < 2 {
474
0
        return Err(Error::from("Unsupported radix < 2"));
475
0
    }
476
0
    if radix > 36 {
477
        // As per trait documentation
478
0
        return Err(Error::from("Unsupported radix > 36"));
479
0
    }
480
481
0
    let mut offset = 0;
482
0
    let mut len = str.len();
483
0
    let bytes = str.as_bytes();
484
0
    let mut negative = false; // assume positive
485
486
    // handle the sign
487
0
    if bytes[offset] == b'-' {
488
0
        negative = true; // leading minus means negative
489
0
        offset += 1;
490
0
        len -= 1;
491
0
    } else if bytes[offset] == b'+' {
492
0
        // leading + allowed
493
0
        offset += 1;
494
0
        len -= 1;
495
0
    }
496
497
    // should now be at numeric part of the significand
498
0
    let mut digits_before_dot: i32 = -1; // digits before '.', -1 if no '.'
499
0
    let mut coeff = ArrayVec::<_, 96>::new(); // integer significand array
500
501
    // Supporting different radix
502
0
    let (max_n, max_alpha_lower, max_alpha_upper) = if radix <= 10 {
503
0
        (b'0' + (radix - 1) as u8, 0, 0)
504
    } else {
505
0
        let adj = (radix - 11) as u8;
506
0
        (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
0
    let estimated_max_precision = match radix {
513
0
        2 => 96,
514
0
        3 => 61,
515
0
        4 => 48,
516
0
        5 => 42,
517
0
        6 => 38,
518
0
        7 => 35,
519
0
        8 => 32,
520
0
        9 => 31,
521
0
        10 => 28,
522
0
        11 => 28,
523
0
        12 => 27,
524
0
        13 => 26,
525
0
        14 => 26,
526
0
        15 => 25,
527
0
        16 => 24,
528
0
        17 => 24,
529
0
        18 => 24,
530
0
        19 => 23,
531
0
        20 => 23,
532
0
        21 => 22,
533
0
        22 => 22,
534
0
        23 => 22,
535
0
        24 => 21,
536
0
        25 => 21,
537
0
        26 => 21,
538
0
        27 => 21,
539
0
        28 => 20,
540
0
        29 => 20,
541
0
        30 => 20,
542
0
        31 => 20,
543
0
        32 => 20,
544
0
        33 => 20,
545
0
        34 => 19,
546
0
        35 => 19,
547
0
        36 => 19,
548
0
        _ => return Err(Error::from("Unsupported radix")),
549
    };
550
551
0
    let mut maybe_round = false;
552
0
    while len > 0 {
553
0
        let b = bytes[offset];
554
0
        match b {
555
0
            b'0'..=b'9' => {
556
0
                if b > max_n {
557
0
                    return Err(Error::from("Invalid decimal: invalid character"));
558
0
                }
559
0
                coeff.push(u32::from(b - b'0'));
560
0
                offset += 1;
561
0
                len -= 1;
562
563
                // If the coefficient is longer than the max, exit early
564
0
                if coeff.len() as u32 > estimated_max_precision {
565
0
                    maybe_round = true;
566
0
                    break;
567
0
                }
568
            }
569
0
            b'a'..=b'z' => {
570
0
                if b > max_alpha_lower {
571
0
                    return Err(Error::from("Invalid decimal: invalid character"));
572
0
                }
573
0
                coeff.push(u32::from(b - b'a') + 10);
574
0
                offset += 1;
575
0
                len -= 1;
576
577
0
                if coeff.len() as u32 > estimated_max_precision {
578
0
                    maybe_round = true;
579
0
                    break;
580
0
                }
581
            }
582
0
            b'A'..=b'Z' => {
583
0
                if b > max_alpha_upper {
584
0
                    return Err(Error::from("Invalid decimal: invalid character"));
585
0
                }
586
0
                coeff.push(u32::from(b - b'A') + 10);
587
0
                offset += 1;
588
0
                len -= 1;
589
590
0
                if coeff.len() as u32 > estimated_max_precision {
591
0
                    maybe_round = true;
592
0
                    break;
593
0
                }
594
            }
595
            b'.' => {
596
0
                if digits_before_dot >= 0 {
597
0
                    return Err(Error::from("Invalid decimal: two decimal points"));
598
0
                }
599
0
                digits_before_dot = coeff.len() as i32;
600
0
                offset += 1;
601
0
                len -= 1;
602
            }
603
            b'_' => {
604
                // Must start with a number...
605
0
                if coeff.is_empty() {
606
0
                    return Err(Error::from("Invalid decimal: must start lead with a number"));
607
0
                }
608
0
                offset += 1;
609
0
                len -= 1;
610
            }
611
0
            _ => 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
0
    if maybe_round && offset < bytes.len() {
617
0
        let next_byte = bytes[offset];
618
0
        let digit = match next_byte {
619
0
            b'0'..=b'9' => {
620
0
                if next_byte > max_n {
621
0
                    return Err(Error::from("Invalid decimal: invalid character"));
622
0
                }
623
0
                u32::from(next_byte - b'0')
624
            }
625
0
            b'a'..=b'z' => {
626
0
                if next_byte > max_alpha_lower {
627
0
                    return Err(Error::from("Invalid decimal: invalid character"));
628
0
                }
629
0
                u32::from(next_byte - b'a') + 10
630
            }
631
0
            b'A'..=b'Z' => {
632
0
                if next_byte > max_alpha_upper {
633
0
                    return Err(Error::from("Invalid decimal: invalid character"));
634
0
                }
635
0
                u32::from(next_byte - b'A') + 10
636
            }
637
0
            b'_' => 0,
638
            b'.' => {
639
                // Still an error if we have a second dp
640
0
                if digits_before_dot >= 0 {
641
0
                    return Err(Error::from("Invalid decimal: two decimal points"));
642
0
                }
643
0
                0
644
            }
645
0
            _ => return Err(Error::from("Invalid decimal: unknown character")),
646
        };
647
648
        // Round at midpoint
649
0
        let midpoint = if radix & 0x1 == 1 { radix / 2 } else { (radix + 1) / 2 };
650
0
        if digit >= midpoint {
651
0
            let mut index = coeff.len() - 1;
652
            loop {
653
0
                let new_digit = coeff[index] + 1;
654
0
                if new_digit <= 9 {
655
0
                    coeff[index] = new_digit;
656
0
                    break;
657
                } else {
658
0
                    coeff[index] = 0;
659
0
                    if index == 0 {
660
0
                        coeff.insert(0, 1u32);
661
0
                        digits_before_dot += 1;
662
0
                        coeff.pop();
663
0
                        break;
664
0
                    }
665
                }
666
0
                index -= 1;
667
            }
668
0
        }
669
0
    }
670
671
    // here when no characters left
672
0
    if coeff.is_empty() {
673
0
        return Err(Error::from("Invalid decimal: no digits found"));
674
0
    }
675
676
0
    let mut scale = if digits_before_dot >= 0 {
677
        // we had a decimal place so set the scale
678
0
        (coeff.len() as u32) - (digits_before_dot as u32)
679
    } else {
680
0
        0
681
    };
682
683
    // Parse this using specified radix
684
0
    let mut data = [0u32, 0u32, 0u32];
685
0
    let mut tmp = [0u32, 0u32, 0u32];
686
0
    let len = coeff.len();
687
0
    for (i, digit) in coeff.iter().enumerate() {
688
        // If the data is going to overflow then we should go into recovery mode
689
0
        tmp[0] = data[0];
690
0
        tmp[1] = data[1];
691
0
        tmp[2] = data[2];
692
0
        let overflow = mul_by_u32(&mut tmp, radix);
693
0
        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
0
            if (i as i32) < digits_before_dot && i + 1 < len {
698
0
                return Err(Error::from("Invalid decimal: overflow from too many digits"));
699
0
            }
700
701
0
            if *digit >= 5 {
702
0
                let carry = add_one_internal(&mut data);
703
0
                if carry > 0 {
704
                    // Highly unlikely scenario which is more indicative of a bug
705
0
                    return Err(Error::from("Invalid decimal: overflow when rounding"));
706
0
                }
707
0
            }
708
            // We're also one less digit so reduce the scale
709
0
            let diff = (len - i) as u32;
710
0
            if diff > scale {
711
0
                return Err(Error::from("Invalid decimal: overflow from scale mismatch"));
712
0
            }
713
0
            scale -= diff;
714
0
            break;
715
        } else {
716
0
            data[0] = tmp[0];
717
0
            data[1] = tmp[1];
718
0
            data[2] = tmp[2];
719
0
            let carry = add_by_internal_flattened(&mut data, *digit);
720
0
            if carry > 0 {
721
                // Highly unlikely scenario which is more indicative of a bug
722
0
                return Err(Error::from("Invalid decimal: overflow from carry"));
723
0
            }
724
        }
725
    }
726
727
0
    Ok(Decimal::from_parts(data[0], data[1], data[2], negative, scale))
728
0
}
729
730
#[cfg(test)]
731
mod 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
}