darkfi/util/
parse.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2026 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use std::str::FromStr;
20
21use crate::{Error, Result};
22
23pub fn decode_base10(amount: &str, decimal_places: usize, strict: bool) -> Result<u64> {
24    let mut s: Vec<char> = amount.to_string().chars().collect();
25
26    // Get rid of the decimal point:
27    let point: usize = if let Some(p) = amount.find('.') {
28        s.remove(p);
29        p
30    } else {
31        s.len()
32    };
33
34    // Only digits should remain
35    for i in &s {
36        if !i.is_ascii_digit() {
37            return Err(Error::ParseFailed("Found non-digits"))
38        }
39    }
40
41    // Add digits to the end if there are too few:
42    let actual_places = s.len() - point;
43    if actual_places < decimal_places {
44        s.extend(vec!['0'; decimal_places - actual_places])
45    }
46
47    // Remove digits from the end if there are too many:
48    let mut round = false;
49    if actual_places > decimal_places {
50        let end = point + decimal_places;
51        for i in &s[end..s.len()] {
52            if *i != '0' {
53                round = true;
54                break
55            }
56        }
57        s.truncate(end);
58    }
59
60    if strict && round {
61        return Err(Error::ParseFailed("Would end up rounding while strict"))
62    }
63
64    // Convert to an integer
65    let number = u64::from_str(&String::from_iter(&s))?;
66
67    // Round and return
68    if round && number == u64::MAX {
69        return Err(Error::ParseFailed("u64 overflow"))
70    }
71
72    Ok(number + round as u64)
73}
74
75pub fn encode_base10(amount: u64, decimal_places: usize) -> String {
76    let mut s: Vec<char> =
77        format!("{:0width$}", amount, width = 1 + decimal_places).chars().collect();
78    s.insert(s.len() - decimal_places, '.');
79
80    String::from_iter(&s).trim_end_matches('0').trim_end_matches('.').to_string()
81}
82
83#[cfg(test)]
84mod tests {
85    use super::{decode_base10, encode_base10};
86
87    #[test]
88    fn test_decode_base10() {
89        assert_eq!(124, decode_base10("12.33", 1, false).unwrap());
90        assert_eq!(1233000, decode_base10("12.33", 5, false).unwrap());
91        assert_eq!(1200000, decode_base10("12.", 5, false).unwrap());
92        assert_eq!(1200000, decode_base10("12", 5, false).unwrap());
93        assert!(decode_base10("12.33", 1, true).is_err());
94    }
95
96    #[test]
97    fn test_encode_base10() {
98        assert_eq!("23.4321111", &encode_base10(234321111, 7));
99        assert_eq!("23432111.1", &encode_base10(234321111, 1));
100        assert_eq!("234321.1", &encode_base10(2343211, 1));
101        assert_eq!("2343211", &encode_base10(2343211, 0));
102        assert_eq!("0.00002343", &encode_base10(2343, 8));
103    }
104}