Skip to content

Commit aa2c3d3

Browse files
authored
Fixes error handling when second decimal place at tail position (paupino#636)
* Fixes error handling when second decimal place at tail position * Added further bounds tests
1 parent 80e9f08 commit aa2c3d3

File tree

1 file changed

+108
-11
lines changed

1 file changed

+108
-11
lines changed

src/str.rs

Lines changed: 108 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,14 @@ fn dispatch_next<const POINT: bool, const NEG: bool, const HAS: bool, const BIG:
213213
}
214214
}
215215

216+
/// Dispatch the next non-digit byte:
217+
///
218+
/// * POINT - a decimal point has been seen
219+
/// * NEG - we've encountered a `-` and the number is negative
220+
/// * HAS - a digit has been encountered (when HAS is false it's invalid)
221+
/// * BIG - a number that uses 96 bits instead of only 64 bits
222+
/// * FIRST - true if it is the first byte in the string
223+
/// * ROUND - attempt to round underflow
216224
#[inline(never)]
217225
fn non_digit_dispatch_u64<
218226
const POINT: bool,
@@ -347,7 +355,17 @@ fn handle_full_128<const POINT: bool, const NEG: bool, const ROUND: bool>(
347355
let next = *next;
348356
if POINT && scale >= 28 {
349357
if ROUND {
350-
maybe_round(data, next, scale, POINT, NEG)
358+
// If it is an underscore at the rounding position we require slightly different handling to look ahead another digit
359+
if next == b'_' {
360+
if let Some((next, bytes)) = bytes.split_first() {
361+
handle_full_128::<POINT, NEG, ROUND>(data, bytes, scale, *next)
362+
} else {
363+
handle_data::<NEG, true>(data, scale)
364+
}
365+
} else {
366+
// Otherwise, we round as usual
367+
maybe_round(data, next, scale, POINT, NEG)
368+
}
351369
} else {
352370
Err(Error::Underflow)
353371
}
@@ -380,17 +398,11 @@ fn handle_full_128<const POINT: bool, const NEG: bool, const ROUND: bool>(
380398

381399
#[inline(never)]
382400
#[cold]
383-
fn maybe_round(
384-
mut data: u128,
385-
next_byte: u8,
386-
mut scale: u8,
387-
point: bool,
388-
negative: bool,
389-
) -> Result<Decimal, crate::Error> {
401+
fn maybe_round(mut data: u128, next_byte: u8, mut scale: u8, point: bool, negative: bool) -> Result<Decimal, Error> {
390402
let digit = match next_byte {
391403
b'0'..=b'9' => u32::from(next_byte - b'0'),
392-
b'_' => 0, // this should be an invalid string?
393-
b'.' if point => 0,
404+
b'_' => 0, // This is perhaps an error case, but keep this here for compatibility
405+
b'.' if !point => 0,
394406
b => return tail_invalid_digit(b),
395407
};
396408

@@ -933,7 +945,6 @@ mod test {
933945
);
934946
}
935947

936-
#[ignore]
937948
#[test]
938949
fn from_str_mantissa_overflow_4() {
939950
// Same test as above, however with underscores. This causes issues.
@@ -945,6 +956,92 @@ mod test {
945956
);
946957
}
947958

959+
#[test]
960+
fn invalid_input_1() {
961+
assert_eq!(
962+
parse_str_radix_10("1.0000000000000000000000000000.5"),
963+
Err(Error::from("Invalid decimal: two decimal points"))
964+
);
965+
}
966+
967+
#[test]
968+
fn invalid_input_2() {
969+
assert_eq!(
970+
parse_str_radix_10("1.0.5"),
971+
Err(Error::from("Invalid decimal: two decimal points"))
972+
);
973+
}
974+
975+
#[test]
976+
fn character_at_rounding_position() {
977+
let tests = [
978+
// digit is at the rounding position
979+
(
980+
"1.000_000_000_000_000_000_000_000_000_04",
981+
Ok(Decimal::from_i128_with_scale(
982+
1_000_000_000_000_000_000_000_000_000_0,
983+
28,
984+
)),
985+
),
986+
(
987+
"1.000_000_000_000_000_000_000_000_000_06",
988+
Ok(Decimal::from_i128_with_scale(
989+
1_000_000_000_000_000_000_000_000_000_1,
990+
28,
991+
)),
992+
),
993+
// Decimal point is at the rounding position
994+
(
995+
"1_000_000_000_000_000_000_000_000_000_0.4",
996+
Ok(Decimal::from_i128_with_scale(
997+
1_000_000_000_000_000_000_000_000_000_0,
998+
0,
999+
)),
1000+
),
1001+
(
1002+
"1_000_000_000_000_000_000_000_000_000_0.6",
1003+
Ok(Decimal::from_i128_with_scale(
1004+
1_000_000_000_000_000_000_000_000_000_1,
1005+
0,
1006+
)),
1007+
),
1008+
// Placeholder is at the rounding position
1009+
(
1010+
"1.000_000_000_000_000_000_000_000_000_0_4",
1011+
Ok(Decimal::from_i128_with_scale(
1012+
1_000_000_000_000_000_000_000_000_000_0,
1013+
28,
1014+
)),
1015+
),
1016+
(
1017+
"1.000_000_000_000_000_000_000_000_000_0_6",
1018+
Ok(Decimal::from_i128_with_scale(
1019+
1_000_000_000_000_000_000_000_000_000_1,
1020+
28,
1021+
)),
1022+
),
1023+
// Multiple placeholders at rounding position
1024+
(
1025+
"1.000_000_000_000_000_000_000_000_000_0__4",
1026+
Ok(Decimal::from_i128_with_scale(
1027+
1_000_000_000_000_000_000_000_000_000_0,
1028+
28,
1029+
)),
1030+
),
1031+
(
1032+
"1.000_000_000_000_000_000_000_000_000_0__6",
1033+
Ok(Decimal::from_i128_with_scale(
1034+
1_000_000_000_000_000_000_000_000_000_1,
1035+
28,
1036+
)),
1037+
),
1038+
];
1039+
1040+
for (input, expected) in tests.iter() {
1041+
assert_eq!(parse_str_radix_10(input), *expected, "Test input {}", input);
1042+
}
1043+
}
1044+
9481045
#[test]
9491046
fn from_str_edge_cases_1() {
9501047
assert_eq!(parse_str_radix_10(""), Err(Error::from("Invalid decimal: empty")));

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy