From 114e35ef308cca6f028f79bf7e620bb462266db2 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 9 Apr 2024 07:26:27 +0300 Subject: [PATCH 1/6] Added decimal casting for the E notation --- arrow-cast/src/parse.rs | 224 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 212 insertions(+), 12 deletions(-) diff --git a/arrow-cast/src/parse.rs b/arrow-cast/src/parse.rs index afa00f176293..2c1f9e1cee48 100644 --- a/arrow-cast/src/parse.rs +++ b/arrow-cast/src/parse.rs @@ -671,6 +671,105 @@ impl Parser for Date64Type { } } +fn parse_e_notation ( + s: &str, + mut digits: u16, + mut fractionals: i16, + mut result: T::Native, + precision: u16, + scale: i16 +) -> Result { + if digits == 0 && fractionals == 0{ + return Err(ArrowError::ParseError(format!("can't parse the string value {s} to decimal"))); + } + + let mut exp: i16 = 0; + let base = T::Native::usize_as(10); + + let mut exp_start: bool = false; + // e has a plus sign + let mut pos_shift_direction: bool = true; + + let mut bs = s.as_bytes().iter().skip((digits + 1) as usize); + + while let Some(b) = bs.next() { + match b { + b'0'..=b'9' => { + result = result.mul_wrapping(base); + result = result.add_wrapping(T::Native::usize_as((b - b'0') as usize)); + if fractionals > 0 { + fractionals += 1; + } + digits += 1; + } + &b'e' | &b'E' => { + exp_start = true; + } + _ => { + return Err(ArrowError::ParseError(format!("can't parse the string value {s} to decimal"))); + } + }; + + if exp_start { + pos_shift_direction = match bs.next() { + Some(&b'-') => false, + Some(&b'+') => true, + Some(b) => { + if !b.is_ascii_digit() { + return Err(ArrowError::ParseError(format!("can't parse the string value {s} to decimal"))); + } + + exp *= 10; + exp += (b - b'0') as i16; + + true + }, + None => return Err(ArrowError::ParseError(format!("can't parse the string value {s} to decimal"))) + }; + + for b in bs.by_ref() { + if !b.is_ascii_digit() { + return Err(ArrowError::ParseError(format!("can't parse the string value {s} to decimal"))); + } + exp *= 10; + exp += (b - b'0') as i16; + } + } + } + + if !pos_shift_direction { + // exponent has a large negative sign + // 1.12345e-30 => 0.0{29}12345, scale = 5 + if exp - (digits as i16 + scale) > 0 { + return Ok(T::Native::usize_as(0)); + } + exp *= -1; + } + + // comma offset + exp = fractionals - exp; + // We have zeros on the left, we need to count them + if !pos_shift_direction && exp > digits as i16 { + digits = exp as u16; + } + // Number of numbers to be removed or added + exp = scale - exp; + + if (digits as i16 + exp) as u16 > precision { + return Err(ArrowError::ParseError(format!("parse decimal overflow ({s})"))); + } + + + if exp < 0 { + result = result.div_wrapping(base.pow_wrapping((exp * -1) as _)); + } else { + result = result.mul_wrapping(base.pow_wrapping(exp as _)); + } + + Ok(result) +} + + /// Parse the string format decimal value to i128/i256 format and checking the precision and scale. /// The result value can't be out of bounds. pub fn parse_decimal( @@ -679,8 +778,8 @@ pub fn parse_decimal( scale: i8, ) -> Result { let mut result = T::Native::usize_as(0); - let mut fractionals = 0; - let mut digits = 0; + let mut fractionals: i8 = 0; + let mut digits: u8 = 0; let base = T::Native::usize_as(10); let bs = s.as_bytes(); @@ -696,7 +795,10 @@ pub fn parse_decimal( ))); } + let mut is_e_notation = false; + let mut bs = bs.iter(); + // Overflow checks are not required if 10^(precision - 1) <= T::MAX holds. // Thus, if we validate the precision correctly, we can skip overflow checks. while let Some(b) = bs.next() { @@ -713,6 +815,23 @@ pub fn parse_decimal( b'.' => { for b in bs.by_ref() { if !b.is_ascii_digit() { + if *b == b'e' || *b == b'E' { + result = match parse_e_notation::( + s, + digits.clone() as u16, + fractionals.clone() as i16, + result, + precision.clone() as u16, + scale.clone() as i16, + ) { + Err(e) => return Err(e), + Ok(v) => v + }; + + is_e_notation = true; + + break; + } return Err(ArrowError::ParseError(format!( "can't parse the string value {s} to decimal" ))); @@ -729,6 +848,10 @@ pub fn parse_decimal( result = result.add_wrapping(T::Native::usize_as((b - b'0') as usize)); } + if is_e_notation { + break + } + // Fail on "." if digits == 0 { return Err(ArrowError::ParseError(format!( @@ -736,6 +859,23 @@ pub fn parse_decimal( ))); } } + b'e' | b'E' => { + result = match parse_e_notation::( + s, + digits.clone() as u16, + fractionals.clone() as i16, + result, + precision.clone() as u16, + scale.clone() as i16 + ) { + Err(e) => return Err(e), + Ok(v) => v + }; + + is_e_notation = true; + + break; + } _ => { return Err(ArrowError::ParseError(format!( "can't parse the string value {s} to decimal" @@ -744,15 +884,17 @@ pub fn parse_decimal( } } - if fractionals < scale { - let exp = scale - fractionals; - if exp as u8 + digits > precision { - return Err(ArrowError::ParseError("parse decimal overflow".to_string())); + if !is_e_notation { + if fractionals < scale { + let exp = scale - fractionals; + if exp as u8 + digits > precision { + return Err(ArrowError::ParseError(format!("parse decimal overflow ({s})"))); + } + let mul = base.pow_wrapping(exp as _); + result = result.mul_wrapping(mul); + } else if digits > precision { + return Err(ArrowError::ParseError(format!("parse decimal overflow ({s})"))); } - let mul = base.pow_wrapping(exp as _); - result = result.mul_wrapping(mul); - } else if digits > precision { - return Err(ArrowError::ParseError("parse decimal overflow".to_string())); } Ok(if negative { @@ -762,6 +904,7 @@ pub fn parse_decimal( }) } + pub fn parse_interval_year_month( value: &str, ) -> Result<::Native, ArrowError> { @@ -2202,7 +2345,38 @@ mod tests { let result_256 = parse_decimal::(s, 20, 3); assert_eq!(i256::from_i128(i), result_256.unwrap()); } - let can_not_parse_tests = ["123,123", ".", "123.123.123", "", "+", "-"]; + let e_notation_tests = [ + ("1.23e3", "1230.0", 2), + ("5.6714e+2", "567.14", 4), + ("5.6714e-2", "0.056714", 4), + ("5.6714e-2", "0.056714", 3), + ("5.6741214125e2", "567.41214125", 4), + ("8.91E4", "89100.0", 2), + ("3.14E+5", "314000.0", 2), + ("2.718e0", "2.718", 2), + ("9.999999e-1", "0.9999999", 4), + ("1.23e+3", "1230", 2), + ("1.234559e+3", "1234.559", 2), + ("1.00E-10", "0.0000000001", 11), + ("1.23e-4", "0.000123", 2), + ("9.876e7", "98760000.0", 2), + ("5.432E+8", "543200000.0", 10), + ("1.234567e9", "1234567000.0", 2), + ("1.234567e2", "123.45670000", 2), + ("4749.3e-5", "0.047493", 10), + ("4749.3e+5", "474930000", 10), + ("4749.3e-5", "0.047493", 1), + ("4749.3e+5", "474930000", 1), + ]; + for (e, d, scale) in e_notation_tests { + let result_128_e = parse_decimal::(e, 20, scale); + let result_128_d = parse_decimal::(d, 20, scale); + assert_eq!(result_128_e.unwrap(), result_128_d.unwrap()); + let result_256_e = parse_decimal::(e, 20, scale); + let result_256_d = parse_decimal::(d, 20, scale); + assert_eq!(result_256_e.unwrap(), result_256_d.unwrap()); + } + let can_not_parse_tests = ["123,123", ".", "123.123.123", "", "+", "-", "e", "1.3e+e3"]; for s in can_not_parse_tests { let result_128 = parse_decimal::(s, 20, 3); assert_eq!( @@ -2215,7 +2389,7 @@ mod tests { result_256.unwrap_err().to_string() ); } - let overflow_parse_tests = ["12345678", "12345678.9", "99999999.99"]; + let overflow_parse_tests = ["12345678", "12345678.9", "99999999.99", "9.999999999e7"]; for s in overflow_parse_tests { let result_128 = parse_decimal::(s, 10, 3); let expected_128 = "Parser error: parse decimal overflow"; @@ -2262,6 +2436,16 @@ mod tests { 99999999999999999999999999999999999999i128, 38, ), + ( + "0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001016744", + 0i128, + 15, + ), + ( + "1.016744e-320", + 0i128, + 15, + ), ]; for (s, i, scale) in edge_tests_128 { let result_128 = parse_decimal::(s, 38, scale); @@ -2292,6 +2476,14 @@ mod tests { .unwrap(), 26, ), + ( + "9.999999999999999999999999999999999999999999999999999999999999999999999999999e49", + i256::from_string( + "9999999999999999999999999999999999999999999999999999999999999999999999999999", + ) + .unwrap(), + 26, + ), ( "99999999999999999999999999999999999999999999999999", i256::from_string( @@ -2300,6 +2492,14 @@ mod tests { .unwrap(), 26, ), + ( + "9.9999999999999999999999999999999999999999999999999e+49", + i256::from_string( + "9999999999999999999999999999999999999999999999999900000000000000000000000000", + ) + .unwrap(), + 26, + ), ]; for (s, i, scale) in edge_tests_256 { let result = parse_decimal::(s, 76, scale); From 736988cf9f4a71295f6f000e72db431a4d5b7c02 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 9 Apr 2024 13:46:35 +0300 Subject: [PATCH 2/6] Clippy --- arrow-cast/src/parse.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/arrow-cast/src/parse.rs b/arrow-cast/src/parse.rs index 2c1f9e1cee48..df5229024e8d 100644 --- a/arrow-cast/src/parse.rs +++ b/arrow-cast/src/parse.rs @@ -761,7 +761,7 @@ fn parse_e_notation ( if exp < 0 { - result = result.div_wrapping(base.pow_wrapping((exp * -1) as _)); + result = result.div_wrapping(base.pow_wrapping(-exp as _)); } else { result = result.mul_wrapping(base.pow_wrapping(exp as _)); } @@ -818,11 +818,11 @@ pub fn parse_decimal( if *b == b'e' || *b == b'E' { result = match parse_e_notation::( s, - digits.clone() as u16, - fractionals.clone() as i16, + digits as u16, + fractionals as i16, result, - precision.clone() as u16, - scale.clone() as i16, + precision as u16, + scale as i16, ) { Err(e) => return Err(e), Ok(v) => v @@ -862,11 +862,11 @@ pub fn parse_decimal( b'e' | b'E' => { result = match parse_e_notation::( s, - digits.clone() as u16, - fractionals.clone() as i16, + digits as u16, + fractionals as i16, result, - precision.clone() as u16, - scale.clone() as i16 + precision as u16, + scale as i16 ) { Err(e) => return Err(e), Ok(v) => v From 0661c9ebd03cfc4b739420c52ab846835f0d334d Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 9 Apr 2024 14:17:23 +0300 Subject: [PATCH 3/6] Fmt --- arrow-cast/src/parse.rs | 53 ++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/arrow-cast/src/parse.rs b/arrow-cast/src/parse.rs index df5229024e8d..e0f838a91fbe 100644 --- a/arrow-cast/src/parse.rs +++ b/arrow-cast/src/parse.rs @@ -671,16 +671,18 @@ impl Parser for Date64Type { } } -fn parse_e_notation ( +fn parse_e_notation( s: &str, mut digits: u16, mut fractionals: i16, mut result: T::Native, precision: u16, - scale: i16 + scale: i16, ) -> Result { - if digits == 0 && fractionals == 0{ - return Err(ArrowError::ParseError(format!("can't parse the string value {s} to decimal"))); + if digits == 0 && fractionals == 0 { + return Err(ArrowError::ParseError(format!( + "can't parse the string value {s} to decimal" + ))); } let mut exp: i16 = 0; @@ -706,7 +708,9 @@ fn parse_e_notation ( exp_start = true; } _ => { - return Err(ArrowError::ParseError(format!("can't parse the string value {s} to decimal"))); + return Err(ArrowError::ParseError(format!( + "can't parse the string value {s} to decimal" + ))); } }; @@ -716,20 +720,28 @@ fn parse_e_notation ( Some(&b'+') => true, Some(b) => { if !b.is_ascii_digit() { - return Err(ArrowError::ParseError(format!("can't parse the string value {s} to decimal"))); + return Err(ArrowError::ParseError(format!( + "can't parse the string value {s} to decimal" + ))); } exp *= 10; exp += (b - b'0') as i16; true - }, - None => return Err(ArrowError::ParseError(format!("can't parse the string value {s} to decimal"))) + } + None => { + return Err(ArrowError::ParseError(format!( + "can't parse the string value {s} to decimal" + ))) + } }; for b in bs.by_ref() { if !b.is_ascii_digit() { - return Err(ArrowError::ParseError(format!("can't parse the string value {s} to decimal"))); + return Err(ArrowError::ParseError(format!( + "can't parse the string value {s} to decimal" + ))); } exp *= 10; exp += (b - b'0') as i16; @@ -756,10 +768,11 @@ fn parse_e_notation ( exp = scale - exp; if (digits as i16 + exp) as u16 > precision { - return Err(ArrowError::ParseError(format!("parse decimal overflow ({s})"))); + return Err(ArrowError::ParseError(format!( + "parse decimal overflow ({s})" + ))); } - if exp < 0 { result = result.div_wrapping(base.pow_wrapping(-exp as _)); } else { @@ -769,7 +782,6 @@ fn parse_e_notation ( Ok(result) } - /// Parse the string format decimal value to i128/i256 format and checking the precision and scale. /// The result value can't be out of bounds. pub fn parse_decimal( @@ -825,7 +837,7 @@ pub fn parse_decimal( scale as i16, ) { Err(e) => return Err(e), - Ok(v) => v + Ok(v) => v, }; is_e_notation = true; @@ -849,7 +861,7 @@ pub fn parse_decimal( } if is_e_notation { - break + break; } // Fail on "." @@ -866,10 +878,10 @@ pub fn parse_decimal( fractionals as i16, result, precision as u16, - scale as i16 + scale as i16, ) { Err(e) => return Err(e), - Ok(v) => v + Ok(v) => v, }; is_e_notation = true; @@ -888,12 +900,16 @@ pub fn parse_decimal( if fractionals < scale { let exp = scale - fractionals; if exp as u8 + digits > precision { - return Err(ArrowError::ParseError(format!("parse decimal overflow ({s})"))); + return Err(ArrowError::ParseError(format!( + "parse decimal overflow ({s})" + ))); } let mul = base.pow_wrapping(exp as _); result = result.mul_wrapping(mul); } else if digits > precision { - return Err(ArrowError::ParseError(format!("parse decimal overflow ({s})"))); + return Err(ArrowError::ParseError(format!( + "parse decimal overflow ({s})" + ))); } } @@ -904,7 +920,6 @@ pub fn parse_decimal( }) } - pub fn parse_interval_year_month( value: &str, ) -> Result<::Native, ArrowError> { From af436ae643cadbd9e8f0ca5de0f1a15143ac90b6 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 9 Apr 2024 19:27:53 +0300 Subject: [PATCH 4/6] Added several new tests Fixed error e without fractional part (e.g. 0e-8, 1e7) --- arrow-cast/src/parse.rs | 66 ++++++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/arrow-cast/src/parse.rs b/arrow-cast/src/parse.rs index e0f838a91fbe..da43c2388041 100644 --- a/arrow-cast/src/parse.rs +++ b/arrow-cast/src/parse.rs @@ -679,20 +679,29 @@ fn parse_e_notation( precision: u16, scale: i16, ) -> Result { - if digits == 0 && fractionals == 0 { - return Err(ArrowError::ParseError(format!( - "can't parse the string value {s} to decimal" - ))); - } - let mut exp: i16 = 0; let base = T::Native::usize_as(10); let mut exp_start: bool = false; // e has a plus sign let mut pos_shift_direction: bool = true; - - let mut bs = s.as_bytes().iter().skip((digits + 1) as usize); + let mut bs; + + // no fractional part + if fractionals == 0 { + bs = match digits { + // The first number is 0, for example 0E-3 + 0 => {s.as_bytes().iter().skip(0)}, + _ => {s.as_bytes().iter().skip(digits as usize)}, + } + } else { + // digits + 1 because of the period in the string + bs = s.as_bytes().iter().skip((digits + 1) as usize); + // check for leading 0, because in parse_decimal function zeros before the period are not counted + if digits == fractionals as u16 { + bs.next(); + }; + } while let Some(b) = bs.next() { match b { @@ -749,6 +758,12 @@ fn parse_e_notation( } } + if digits == 0 && fractionals == 0 && exp == 0 { + return Err(ArrowError::ParseError(format!( + "can't parse the string value {s} to decimal" + ))); + } + if !pos_shift_direction { // exponent has a large negative sign // 1.12345e-30 => 0.0{29}12345, scale = 5 @@ -758,7 +773,7 @@ fn parse_e_notation( exp *= -1; } - // comma offset + // period offset exp = fractionals - exp; // We have zeros on the left, we need to count them if !pos_shift_direction && exp > digits as i16 { @@ -2382,6 +2397,11 @@ mod tests { ("4749.3e+5", "474930000", 10), ("4749.3e-5", "0.047493", 1), ("4749.3e+5", "474930000", 1), + ("0E-8", "0", 10), + ("0E+6", "0", 10), + ("1E-8", "0.00000001", 10), + ("12E+6", "12000000", 10), + ("0.1e-6", "0.0000001", 10), ]; for (e, d, scale) in e_notation_tests { let result_128_e = parse_decimal::(e, 20, scale); @@ -2391,7 +2411,22 @@ mod tests { let result_256_d = parse_decimal::(d, 20, scale); assert_eq!(result_256_e.unwrap(), result_256_d.unwrap()); } - let can_not_parse_tests = ["123,123", ".", "123.123.123", "", "+", "-", "e", "1.3e+e3"]; + let can_not_parse_tests = [ + "123,123", + ".", + "123.123.123", + "", + "+", + "-", + "e", + "1.3e+e3", + "5.6714ee-2", + "4.11ee-+4", + "4.11e++4", + "1.1e.12", + "1.23e+3.", + "1.23e+3.1", + ]; for s in can_not_parse_tests { let result_128 = parse_decimal::(s, 20, 3); assert_eq!( @@ -2404,7 +2439,16 @@ mod tests { result_256.unwrap_err().to_string() ); } - let overflow_parse_tests = ["12345678", "12345678.9", "99999999.99", "9.999999999e7"]; + let overflow_parse_tests = [ + "12345678", + "1.2345678e7", + "12345678.9", + "1.23456789e+7", + "99999999.99", + "9.999999999e7", + "12345678908765.123456", + "123456789087651234.56e-4", + ]; for s in overflow_parse_tests { let result_128 = parse_decimal::(s, 10, 3); let expected_128 = "Parser error: parse decimal overflow"; From 2eb2706e4f11b3f694a2122b8e6faa37acb81703 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 9 Apr 2024 19:38:26 +0300 Subject: [PATCH 5/6] Clippy, fmt --- arrow-cast/src/parse.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/arrow-cast/src/parse.rs b/arrow-cast/src/parse.rs index da43c2388041..5d0bddd7eac1 100644 --- a/arrow-cast/src/parse.rs +++ b/arrow-cast/src/parse.rs @@ -689,11 +689,7 @@ fn parse_e_notation( // no fractional part if fractionals == 0 { - bs = match digits { - // The first number is 0, for example 0E-3 - 0 => {s.as_bytes().iter().skip(0)}, - _ => {s.as_bytes().iter().skip(digits as usize)}, - } + bs = s.as_bytes().iter().skip(digits as usize) } else { // digits + 1 because of the period in the string bs = s.as_bytes().iter().skip((digits + 1) as usize); From 5e0797388c21a0c0cd367f31d2c1fd73cab9d5d2 Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 10 Apr 2024 03:58:21 +0300 Subject: [PATCH 6/6] parse_e_notation function minor rework added additional tests for e/scientific notation --- arrow-cast/src/parse.rs | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/arrow-cast/src/parse.rs b/arrow-cast/src/parse.rs index 5d0bddd7eac1..3102fcc58ada 100644 --- a/arrow-cast/src/parse.rs +++ b/arrow-cast/src/parse.rs @@ -676,6 +676,7 @@ fn parse_e_notation( mut digits: u16, mut fractionals: i16, mut result: T::Native, + index: usize, precision: u16, scale: i16, ) -> Result { @@ -685,18 +686,15 @@ fn parse_e_notation( let mut exp_start: bool = false; // e has a plus sign let mut pos_shift_direction: bool = true; - let mut bs; - // no fractional part - if fractionals == 0 { - bs = s.as_bytes().iter().skip(digits as usize) + // skip to point or exponent index + let mut bs; + if fractionals > 0 { + // it's a fraction, so the point index needs to be skipped, so +1 + bs = s.as_bytes().iter().skip(index + fractionals as usize + 1); } else { - // digits + 1 because of the period in the string - bs = s.as_bytes().iter().skip((digits + 1) as usize); - // check for leading 0, because in parse_decimal function zeros before the period are not counted - if digits == fractionals as u16 { - bs.next(); - }; + // it's actually an integer that is already written into the result, so let's skip on to e + bs = s.as_bytes().iter().skip(index); } while let Some(b) = bs.next() { @@ -769,7 +767,7 @@ fn parse_e_notation( exp *= -1; } - // period offset + // point offset exp = fractionals - exp; // We have zeros on the left, we need to count them if !pos_shift_direction && exp > digits as i16 { @@ -818,13 +816,13 @@ pub fn parse_decimal( ))); } - let mut is_e_notation = false; + let mut bs = bs.iter().enumerate(); - let mut bs = bs.iter(); + let mut is_e_notation = false; // Overflow checks are not required if 10^(precision - 1) <= T::MAX holds. // Thus, if we validate the precision correctly, we can skip overflow checks. - while let Some(b) = bs.next() { + while let Some((index, b)) = bs.next() { match b { b'0'..=b'9' => { if digits == 0 && *b == b'0' { @@ -836,7 +834,9 @@ pub fn parse_decimal( result = result.add_wrapping(T::Native::usize_as((b - b'0') as usize)); } b'.' => { - for b in bs.by_ref() { + let point_index = index; + + for (_, b) in bs.by_ref() { if !b.is_ascii_digit() { if *b == b'e' || *b == b'E' { result = match parse_e_notation::( @@ -844,6 +844,7 @@ pub fn parse_decimal( digits as u16, fractionals as i16, result, + point_index, precision as u16, scale as i16, ) { @@ -888,6 +889,7 @@ pub fn parse_decimal( digits as u16, fractionals as i16, result, + index, precision as u16, scale as i16, ) { @@ -2371,6 +2373,7 @@ mod tests { let result_256 = parse_decimal::(s, 20, 3); assert_eq!(i256::from_i128(i), result_256.unwrap()); } + let e_notation_tests = [ ("1.23e3", "1230.0", 2), ("5.6714e+2", "567.14", 4), @@ -2397,7 +2400,13 @@ mod tests { ("0E+6", "0", 10), ("1E-8", "0.00000001", 10), ("12E+6", "12000000", 10), + ("12E-6", "0.000012", 10), ("0.1e-6", "0.0000001", 10), + ("0.1e+6", "100000", 10), + ("0.12e-6", "0.00000012", 10), + ("0.12e+6", "120000", 10), + ("000000000001e0", "000000000001", 3), + ("000001.1034567002e0", "000001.1034567002", 3), ]; for (e, d, scale) in e_notation_tests { let result_128_e = parse_decimal::(e, 20, scale);