use std::fmt::Display;
use std::error::Error;
use std::cmp::{PartialEq, Eq, PartialOrd, Ord, Ordering};
#[derive(Debug, Clone, Copy)]
pub enum ConvertionError {
InvalidBase,
InvalidInteger,
}
impl Display for ConvertionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let err_msg = match self {
Self::InvalidBase => "The base isn't in range [2;36]",
Self::InvalidInteger => "Incorrect number",
};
write!(f, "{err_msg}")
}
}
impl Error for ConvertionError {}
#[derive(Debug, Clone)]
pub struct Number {
number: String,
base: u32,
}
impl Number {
pub fn new(number: &str, base: u32) -> Result<Self, ConvertionError> {
let num = Self {
number: number.to_string(),
base,
};
num.convert(base)
}
pub fn convert(&self, base: u32) -> Result<Self, ConvertionError> {
if base > 36 { return Err(ConvertionError::InvalidBase); }
let is_negative = self.number.starts_with('-');
let number = if is_negative { &self.number[1..] } else { &self.number };
let in_decimal = Self::to_decimal(number, self.base)?;
let mut in_needed_base = Self::from_decimal(in_decimal, base)?;
if is_negative { in_needed_base.insert(0, '-'); }
Ok(Self { number: in_needed_base, base })
}
pub fn number(&self) -> String {
self.number.clone()
}
pub fn base(&self) -> u32 {
self.base
}
fn to_decimal(number_str: &str, base: u32) -> Result<u32, ConvertionError> {
if !(2..=36).contains(&base) { return Err(ConvertionError::InvalidBase); }
u32::from_str_radix(number_str, base).map_err(|_| ConvertionError::InvalidInteger)
}
fn from_decimal(mut number: u32, base: u32) -> Result<String, ConvertionError> {
let mut buff = vec![];
while number != 0 {
let ch = char::from_digit(number % base, base)
.ok_or(ConvertionError::InvalidInteger)?;
buff.push(ch);
number /= base;
}
Ok(buff.iter().rev().collect())
}
}
impl Display for Number {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.number)
}
}
impl PartialEq for Number {
fn eq(&self, other: &Self) -> bool {
let self_num = self.convert(10);
let other_num = other.convert(10);
if self_num.is_err() || other_num.is_err() { return false; }
self_num.unwrap().number() == other_num.unwrap().number()
}
}
impl Eq for Number {}
impl PartialOrd for Number {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
let self_num: i32 = self.convert(10).ok()?.number().parse().ok()?;
let other_num: i32 = other.convert(10).ok()?.number().parse().ok()?;
self_num.partial_cmp(&other_num)
}
}
impl Ord for Number {
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
#[macro_export]
macro_rules! num {
($num:literal, $base:literal) => {
$crate::module10::Number::new($num, $base)
.expect(&format!("Invalid Number! (\"{}\", {})", $num, $base))
};
}