1#[cfg(feature = "wasm")]
4#[allow(non_snake_case)]
5pub mod wasm;
6
7use anyhow::{ensure, Result};
8use thiserror::Error;
9
10pub static BASE2: &str = "01";
12pub static BASE8: &str = "01234567";
14pub static BASE10: &str = "0123456789";
16pub static BASE16: &str = "0123456789ABCDEF";
18
19#[derive(Error, Debug)]
22pub enum CheckBaseError {
23 #[error("length of base '{0}' must be at least 2")]
25 BaseLenTooShort(String),
26 #[error("base '{base}' has at least 2 occurrences of char '{c}'")]
28 DuplicateCharInBase { base: String, c: char },
29}
30
31pub fn check_base(base: &str) -> Result<()> {
47 ensure!(
48 base.chars().count() >= 2,
49 CheckBaseError::BaseLenTooShort(base.into())
50 );
51 for c in base.chars() {
52 ensure!(
53 base.chars().filter(|c2| &c == c2).count() == 1,
54 CheckBaseError::DuplicateCharInBase {
55 base: base.into(),
56 c,
57 }
58 )
59 }
60 Ok(())
61}
62
63#[derive(Error, Debug)]
65pub enum ConversionError {
66 #[error("char '{c}' not found in base '{base}'")]
68 CharNotFoundInBase { base: String, c: char },
69 #[error("base '{base}' of length {base_length} ** {power} overflowed")]
71 ConversionOverflow {
72 base: String,
73 base_length: usize,
74 power: u32,
75 },
76}
77
78pub fn base_to_decimal(nbr: &str, from_base: &str) -> Result<usize> {
91 check_base(from_base)?;
92 let base_length = from_base.chars().count();
93 let mut result: usize = 0;
94 for (c, i) in nbr.chars().zip((0..nbr.chars().count() as u32).rev()) {
95 let x = from_base.chars().position(|x| x == c).ok_or_else(|| {
96 ConversionError::CharNotFoundInBase {
97 base: from_base.into(),
98 c,
99 }
100 })?;
101 result += x
102 * base_length
103 .checked_pow(i)
104 .ok_or_else(|| ConversionError::ConversionOverflow {
105 base: from_base.into(),
106 base_length,
107 power: i,
108 })?;
109 }
110 Ok(result)
111}
112
113pub fn decimal_to_base(mut nbr: usize, to_base: &str) -> Result<String> {
125 check_base(to_base)?;
126 if nbr == 0 {
127 return Ok(to_base.chars().next().unwrap().into());
128 }
129 let base_length = to_base.chars().count();
130 let mut result = String::new();
131 while nbr > 0 {
132 result.push(to_base.chars().nth(nbr % base_length).unwrap());
133 nbr /= base_length;
134 }
135 Ok(result.chars().rev().collect())
136}
137
138pub fn base_to_base(nbr: &str, from_base: &str, to_base: &str) -> Result<String> {
151 let nbr = base_to_decimal(nbr, from_base)?;
152 let nbr = decimal_to_base(nbr, to_base)?;
153 Ok(nbr)
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn test_check_base() {
162 struct TestCase {
163 base: &'static str,
164 err: CheckBaseError,
165 }
166 let test_cases = vec![
167 TestCase {
168 base: "",
169 err: CheckBaseError::BaseLenTooShort("".into()),
170 },
171 TestCase {
172 base: "x",
173 err: CheckBaseError::BaseLenTooShort("x".into()),
174 },
175 TestCase {
176 base: "xx",
177 err: CheckBaseError::DuplicateCharInBase {
178 base: "xx".into(),
179 c: 'x',
180 },
181 },
182 ];
183 for test_case in &test_cases {
184 assert_eq!(
185 format!("{}", check_base(test_case.base).unwrap_err()),
186 format!("{}", test_case.err)
187 );
188 }
189 }
190
191 #[test]
192 fn test_base_to_decimal() {
193 assert_eq!(51966, base_to_decimal("CAFE", BASE16).unwrap());
194 assert_eq!(42, base_to_decimal("101010", BASE2).unwrap());
195 assert_eq!(0, base_to_decimal("0", BASE8).unwrap());
196 assert_eq!(0, base_to_decimal("", BASE8).unwrap());
197 }
198
199 #[test]
200 fn test_decimal_to_base() {
201 assert_eq!("CAFE", decimal_to_base(51966, BASE16).unwrap());
202 assert_eq!("0", decimal_to_base(0, BASE8).unwrap());
203 assert_eq!("x", decimal_to_base(0, "xyz").unwrap());
204 }
205
206 #[test]
207 fn test_base_to_base() {
208 let err = base_to_base("25", "01234", "1123").unwrap_err();
209 assert_eq!(
210 format!("{}", err),
211 format!(
212 "{}",
213 ConversionError::CharNotFoundInBase {
214 base: "01234".into(),
215 c: '5'
216 }
217 )
218 );
219 }
220}