@@ -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) ]
217225fn 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