pub trait NumericalBase {
fn position_value(&self) -> u8;
fn includes(&self, ch: char) -> bool;
unsafe fn value_of_unchecked(&self, ch: char) -> u8;
#[inline]
fn value_of(&self, ch: char) -> Option<u8> {
self.includes(ch)
.then(|| unsafe { self.value_of_unchecked(ch) })
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum StandardBase {
Hexadecimal,
Decimal,
Octal,
Binary,
}
impl NumericalBase for StandardBase {
#[inline]
fn position_value(&self) -> u8 {
match self {
Self::Hexadecimal => 16,
Self::Decimal => 10,
Self::Octal => 8,
Self::Binary => 2,
}
}
#[inline]
fn includes(&self, ch: char) -> bool {
match self {
Self::Hexadecimal => ch.is_ascii_hexdigit(),
Self::Decimal => ch.is_ascii_digit(),
Self::Octal => matches!(ch, '0'..='7'),
Self::Binary => ch == '0' || ch == '1',
}
}
#[inline]
#[expect(clippy::arithmetic_side_effects)]
unsafe fn value_of_unchecked(&self, ch: char) -> u8 {
match self {
Self::Hexadecimal => {
let ascii_value = ch as u8;
if ascii_value >= b'A' {
ascii_value.to_ascii_uppercase() - b'A' + 10
} else {
ascii_value - b'0'
}
}
_ => (ch as u8) - b'0',
}
}
}
#[cfg(test)]
mod tests {
use crate::common::numeric::base::{NumericalBase, StandardBase};
fn assert_contains(base: &impl NumericalBase, char: char, value: u8) {
assert!(base.includes(char));
assert_eq!(base.value_of(char), Some(value));
}
#[test]
fn binary() {
let base = StandardBase::Binary;
assert_contains(&base, '0', 0);
assert_contains(&base, '1', 1);
assert!(!base.includes('2'));
assert_eq!(base.position_value(), 2);
}
#[test]
fn octal() {
let base = StandardBase::Octal;
assert_contains(&base, '0', 0);
assert_contains(&base, '1', 1);
assert_contains(&base, '2', 2);
assert_contains(&base, '3', 3);
assert_contains(&base, '4', 4);
assert_contains(&base, '5', 5);
assert_contains(&base, '6', 6);
assert_contains(&base, '7', 7);
assert!(!base.includes('8'));
assert_eq!(base.position_value(), 8);
}
#[test]
fn decimal() {
let base = StandardBase::Decimal;
assert_contains(&base, '0', 0);
assert_contains(&base, '1', 1);
assert_contains(&base, '2', 2);
assert_contains(&base, '3', 3);
assert_contains(&base, '4', 4);
assert_contains(&base, '5', 5);
assert_contains(&base, '6', 6);
assert_contains(&base, '7', 7);
assert_contains(&base, '8', 8);
assert_contains(&base, '9', 9);
assert!(!base.includes('A'));
assert_eq!(base.position_value(), 10);
}
#[test]
fn hexadecimal() {
let base = StandardBase::Hexadecimal;
assert_contains(&base, '0', 0);
assert_contains(&base, '1', 1);
assert_contains(&base, '2', 2);
assert_contains(&base, '3', 3);
assert_contains(&base, '4', 4);
assert_contains(&base, '5', 5);
assert_contains(&base, '6', 6);
assert_contains(&base, '7', 7);
assert_contains(&base, '8', 8);
assert_contains(&base, '9', 9);
assert_contains(&base, 'A', 10);
assert_contains(&base, 'B', 11);
assert_contains(&base, 'C', 12);
assert_contains(&base, 'D', 13);
assert_contains(&base, 'E', 14);
assert_contains(&base, 'F', 15);
assert!(!base.includes('G'));
assert_eq!(base.position_value(), 16);
}
#[test]
fn invalid_base_char() {
assert_eq!(StandardBase::Decimal.value_of('A'), None);
}
}