@@ -213,6 +213,14 @@ fn dispatch_next<const POINT: bool, const NEG: bool, const HAS: bool, const BIG:
213
213
}
214
214
}
215
215
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
216
224
#[ inline( never) ]
217
225
fn non_digit_dispatch_u64 <
218
226
const POINT : bool ,
@@ -347,7 +355,17 @@ fn handle_full_128<const POINT: bool, const NEG: bool, const ROUND: bool>(
347
355
let next = * next;
348
356
if POINT && scale >= 28 {
349
357
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
+ }
351
369
} else {
352
370
Err ( Error :: Underflow )
353
371
}
@@ -380,17 +398,11 @@ fn handle_full_128<const POINT: bool, const NEG: bool, const ROUND: bool>(
380
398
381
399
#[ inline( never) ]
382
400
#[ 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 > {
390
402
let digit = match next_byte {
391
403
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 ,
394
406
b => return tail_invalid_digit ( b) ,
395
407
} ;
396
408
@@ -933,7 +945,6 @@ mod test {
933
945
) ;
934
946
}
935
947
936
- #[ ignore]
937
948
#[ test]
938
949
fn from_str_mantissa_overflow_4 ( ) {
939
950
// Same test as above, however with underscores. This causes issues.
@@ -945,6 +956,92 @@ mod test {
945
956
) ;
946
957
}
947
958
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
+
948
1045
#[ test]
949
1046
fn from_str_edge_cases_1 ( ) {
950
1047
assert_eq ! ( parse_str_radix_10( "" ) , Err ( Error :: from( "Invalid decimal: empty" ) ) ) ;
0 commit comments