near_gas/
utils.rs

1/// Parsing decimal numbers from `&str` type in `u64`.
2/// Function also takes a value of metric prefix in u64 type.
3/// `parse_str` use the `u64` type, and have the same max and min values.
4///
5/// If the fractional part is longer than several zeros in the prefix, it will return the error `DecimalNumberParsingError::LongFractional`.
6///
7/// If the string slice has invalid chars, it will return the error `DecimalNumberParsingError::InvalidNumber`.
8///
9/// If the whole part of the number has a value more than the `u64` maximum value, it will return the error `DecimalNumberParsingError::LongWhole`.
10pub(crate) fn parse_decimal_number(
11    s: &str,
12    pref_const: u64,
13) -> Result<u64, DecimalNumberParsingError> {
14    let (int, fract) = if let Some((whole, fractional)) = s.trim().split_once('.') {
15        let int: u64 = whole
16            .parse()
17            .map_err(|_| DecimalNumberParsingError::InvalidNumber(s.to_owned()))?;
18        let mut fract: u64 = fractional
19            .parse()
20            .map_err(|_| DecimalNumberParsingError::InvalidNumber(s.to_owned()))?;
21        let len = u32::try_from(fractional.len())
22            .map_err(|_| DecimalNumberParsingError::InvalidNumber(s.to_owned()))?;
23        fract = fract
24            .checked_mul(
25                pref_const
26                    .checked_div(10u64.checked_pow(len).ok_or_else(|| {
27                        DecimalNumberParsingError::LongFractional(fractional.to_owned())
28                    })?)
29                    .filter(|n| *n != 0u64)
30                    .ok_or_else(|| {
31                        DecimalNumberParsingError::LongFractional(fractional.to_owned())
32                    })?,
33            )
34            .ok_or_else(|| DecimalNumberParsingError::LongFractional(fractional.to_owned()))?;
35        (int, fract)
36    } else {
37        let int: u64 = s
38            .parse()
39            .map_err(|_| DecimalNumberParsingError::InvalidNumber(s.to_owned()))?;
40        (int, 0)
41    };
42    let result = fract
43        .checked_add(
44            int.checked_mul(pref_const)
45                .ok_or_else(|| DecimalNumberParsingError::LongWhole(int.to_string()))?,
46        )
47        .ok_or_else(|| DecimalNumberParsingError::LongWhole(int.to_string()))?;
48    Ok(result)
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub enum DecimalNumberParsingError {
53    InvalidNumber(String),
54    LongWhole(String),
55    LongFractional(String),
56}
57
58impl std::error::Error for DecimalNumberParsingError {
59    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
60        None
61    }
62
63    fn description(&self) -> &str {
64        "description() is deprecated; use Display"
65    }
66
67    fn cause(&self) -> Option<&dyn std::error::Error> {
68        self.source()
69    }
70}
71
72impl std::fmt::Display for DecimalNumberParsingError {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        match self {
75            DecimalNumberParsingError::InvalidNumber(s) => {
76                write!(f, "Invalid number: {}", s)
77            }
78            DecimalNumberParsingError::LongWhole(s) => {
79                write!(f, "Long whole part: {}", s)
80            }
81            DecimalNumberParsingError::LongFractional(s) => {
82                write!(f, "Long fractional part: {}", s)
83            }
84        }
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    const TEST: [(u64, &str, u64); 6] = [
93        (129_380_000_001_u64, "129.380000001", 10u64.pow(9)),
94        (
95            12_938_000_000_100_000_000_u64,
96            "12938000000.1",
97            10u64.pow(9),
98        ),
99        (129_380_000_001_u64, "0.129380000001", 10u64.pow(12)),
100        (129_380_000_001_000_u64, "129.380000001000", 10u64.pow(12)),
101        (
102            9_488_129_380_000_001_u64,
103            "9488.129380000001",
104            10u64.pow(12),
105        ),
106        (129_380_000_001_u64, "00.129380000001", 10u64.pow(12)),
107    ];
108
109    #[test]
110    fn parse_test() {
111        for (expected_value, str_value, precision) in TEST {
112            let parsed_value = parse_decimal_number(str_value, precision).unwrap();
113            assert_eq!(parsed_value, expected_value)
114        }
115    }
116
117    #[test]
118    fn test_long_fract() {
119        let data = "1.23456";
120        let prefix = 10000u64;
121        assert_eq!(
122            parse_decimal_number(data, prefix),
123            Err(DecimalNumberParsingError::LongFractional(23456.to_string()))
124        );
125    }
126
127    #[test]
128    fn invalidnumber_whole() {
129        let num = "1h4.7859";
130        let prefix: u64 = 10000;
131        assert_eq!(
132            parse_decimal_number(num, prefix),
133            Err(DecimalNumberParsingError::InvalidNumber(
134                "1h4.7859".to_owned()
135            ))
136        );
137    }
138    #[test]
139    fn invalidnumber_fract() {
140        let num = "14.785h9";
141        let prefix: u64 = 10000;
142        assert_eq!(
143            parse_decimal_number(num, prefix),
144            Err(DecimalNumberParsingError::InvalidNumber(
145                "14.785h9".to_owned()
146            ))
147        );
148    }
149
150    #[test]
151    fn max_long_fract() {
152        let max_data = 10u64.pow(17) + 1;
153        let data = "1.".to_string() + max_data.to_string().as_str();
154        let prefix = 10u64.pow(17);
155        assert_eq!(
156            parse_decimal_number(data.as_str(), prefix),
157            Err(DecimalNumberParsingError::LongFractional(
158                max_data.to_string()
159            ))
160        );
161    }
162
163    #[test]
164    fn long_whole_test() {
165        let data = 10u64.pow(17) + 1;
166        let prefix = 10u64.pow(12);
167        let s = data.to_string() + "." + "1";
168        assert_eq!(
169            parse_decimal_number(s.as_str(), prefix),
170            Err(DecimalNumberParsingError::LongWhole(data.to_string()))
171        );
172    }
173
174    #[test]
175    fn parse_u64_errortest() {
176        let test_data = u64::MAX.to_string();
177        let gas = parse_decimal_number(&test_data, 10u64.pow(9));
178        assert_eq!(
179            gas,
180            Err(DecimalNumberParsingError::LongWhole(u64::MAX.to_string()))
181        );
182    }
183
184    #[test]
185    fn test() {
186        let data = "1.000000000000000000000000000000000000001";
187        let prefix = 100u64;
188        assert_eq!(
189            parse_decimal_number(data, prefix),
190            Err(DecimalNumberParsingError::LongFractional(
191                "000000000000000000000000000000000000001".to_string()
192            ))
193        );
194    }
195}