Parse the string format decimal value to i128/i256 format and checking the precision and scale. Expected behavior: - The result value can't be out of bounds. - When parsing a decimal with scale 0, all fractional digits will be discarded. The final fractional digits may be a subset or a superset of the digits after the decimal point when e-notation is used.
(
s: &str,
precision: u8,
scale: i8,
)
| 873 | /// fractional digits may be a subset or a superset of the digits after the decimal point when |
| 874 | /// e-notation is used. |
| 875 | pub fn parse_decimal<T: DecimalType>( |
| 876 | s: &str, |
| 877 | precision: u8, |
| 878 | scale: i8, |
| 879 | ) -> Result<T::Native, ArrowError> { |
| 880 | let mut result = T::Native::usize_as(0); |
| 881 | let mut fractionals: i8 = 0; |
| 882 | let mut digits: u8 = 0; |
| 883 | let base = T::Native::usize_as(10); |
| 884 | |
| 885 | let bs = s.as_bytes(); |
| 886 | |
| 887 | if !bs |
| 888 | .last() |
| 889 | .is_some_and(|b| b.is_ascii_digit() || (b == &b'.' && s.len() > 1)) |
| 890 | { |
| 891 | // If the last character is not a digit (or a decimal point prefixed with some digits), then |
| 892 | // it's not a valid decimal. |
| 893 | return Err(ArrowError::ParseError(format!( |
| 894 | "can't parse the string value {s} to decimal" |
| 895 | ))); |
| 896 | } |
| 897 | |
| 898 | let (signed, negative) = match bs.first() { |
| 899 | Some(b'-') => (true, true), |
| 900 | Some(b'+') => (true, false), |
| 901 | _ => (false, false), |
| 902 | }; |
| 903 | |
| 904 | // Iterate over the raw input bytes, skipping the sign if any |
| 905 | let mut bs = bs.iter().enumerate().skip(signed as usize); |
| 906 | |
| 907 | let mut is_e_notation = false; |
| 908 | |
| 909 | // Overflow checks are not required if 10^(precision - 1) <= T::MAX holds. |
| 910 | // Thus, if we validate the precision correctly, we can skip overflow checks. |
| 911 | while let Some((index, b)) = bs.next() { |
| 912 | match b { |
| 913 | b'0'..=b'9' => { |
| 914 | if digits == 0 && *b == b'0' { |
| 915 | // Ignore leading zeros. |
| 916 | continue; |
| 917 | } |
| 918 | digits += 1; |
| 919 | result = result.mul_wrapping(base); |
| 920 | result = result.add_wrapping(T::Native::usize_as((b - b'0') as usize)); |
| 921 | } |
| 922 | b'.' => { |
| 923 | let point_index = index; |
| 924 | |
| 925 | for (_, b) in bs.by_ref() { |
| 926 | if !b.is_ascii_digit() { |
| 927 | if *b == b'e' || *b == b'E' { |
| 928 | result = parse_e_notation::<T>( |
| 929 | s, |
| 930 | digits as u16, |
| 931 | fractionals as i16, |
| 932 | result, |