diff --git a/src/str.rs b/src/str.rs index 9454573..b381b2a 100644 --- a/src/str.rs +++ b/src/str.rs @@ -213,6 +213,14 @@ fn dispatch_next( let next = *next; if POINT && scale >= 28 { if ROUND { - maybe_round(data, next, scale, POINT, NEG) + // If it is an underscore at the rounding position we require slightly different handling to look ahead another digit + if next == b'_' { + if let Some((next, bytes)) = bytes.split_first() { + handle_full_128::(data, bytes, scale, *next) + } else { + handle_data::(data, scale) + } + } else { + // Otherwise, we round as usual + maybe_round(data, next, scale, POINT, NEG) + } } else { Err(Error::Underflow) } @@ -380,17 +398,11 @@ fn handle_full_128( #[inline(never)] #[cold] -fn maybe_round( - mut data: u128, - next_byte: u8, - mut scale: u8, - point: bool, - negative: bool, -) -> Result { +fn maybe_round(mut data: u128, next_byte: u8, mut scale: u8, point: bool, negative: bool) -> Result { let digit = match next_byte { b'0'..=b'9' => u32::from(next_byte - b'0'), - b'_' => 0, // this should be an invalid string? - b'.' if point => 0, + b'_' => 0, // This is perhaps an error case, but keep this here for compatibility + b'.' if !point => 0, b => return tail_invalid_digit(b), }; @@ -933,7 +945,6 @@ mod test { ); } - #[ignore] #[test] fn from_str_mantissa_overflow_4() { // Same test as above, however with underscores. This causes issues. @@ -945,6 +956,92 @@ mod test { ); } + #[test] + fn invalid_input_1() { + assert_eq!( + parse_str_radix_10("1.0000000000000000000000000000.5"), + Err(Error::from("Invalid decimal: two decimal points")) + ); + } + + #[test] + fn invalid_input_2() { + assert_eq!( + parse_str_radix_10("1.0.5"), + Err(Error::from("Invalid decimal: two decimal points")) + ); + } + + #[test] + fn character_at_rounding_position() { + let tests = [ + // digit is at the rounding position + ( + "1.000_000_000_000_000_000_000_000_000_04", + Ok(Decimal::from_i128_with_scale( + 1_000_000_000_000_000_000_000_000_000_0, + 28, + )), + ), + ( + "1.000_000_000_000_000_000_000_000_000_06", + Ok(Decimal::from_i128_with_scale( + 1_000_000_000_000_000_000_000_000_000_1, + 28, + )), + ), + // Decimal point is at the rounding position + ( + "1_000_000_000_000_000_000_000_000_000_0.4", + Ok(Decimal::from_i128_with_scale( + 1_000_000_000_000_000_000_000_000_000_0, + 0, + )), + ), + ( + "1_000_000_000_000_000_000_000_000_000_0.6", + Ok(Decimal::from_i128_with_scale( + 1_000_000_000_000_000_000_000_000_000_1, + 0, + )), + ), + // Placeholder is at the rounding position + ( + "1.000_000_000_000_000_000_000_000_000_0_4", + Ok(Decimal::from_i128_with_scale( + 1_000_000_000_000_000_000_000_000_000_0, + 28, + )), + ), + ( + "1.000_000_000_000_000_000_000_000_000_0_6", + Ok(Decimal::from_i128_with_scale( + 1_000_000_000_000_000_000_000_000_000_1, + 28, + )), + ), + // Multiple placeholders at rounding position + ( + "1.000_000_000_000_000_000_000_000_000_0__4", + Ok(Decimal::from_i128_with_scale( + 1_000_000_000_000_000_000_000_000_000_0, + 28, + )), + ), + ( + "1.000_000_000_000_000_000_000_000_000_0__6", + Ok(Decimal::from_i128_with_scale( + 1_000_000_000_000_000_000_000_000_000_1, + 28, + )), + ), + ]; + + for (input, expected) in tests.iter() { + assert_eq!(parse_str_radix_10(input), *expected, "Test input {}", input); + } + } + #[test] fn from_str_edge_cases_1() { assert_eq!(parse_str_radix_10(""), Err(Error::from("Invalid decimal: empty")));