parse.rs
1 /* This file is part of DarkFi (https://dark.fi) 2 * 3 * Copyright (C) 2020-2025 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 19 use std::str::FromStr; 20 21 use crate::{Error, Result}; 22 23 pub 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 75 pub 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)] 84 mod 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 }