#![cfg_attr(not(feature = "std"), no_std)]
#![warn(missing_docs)]
#![warn(clippy::std_instead_of_core)]
#![warn(clippy::print_stderr)]
#![warn(clippy::print_stdout)]
use crate::{OutOfRangeError, Result};
use core::fmt;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct RomanNumeral(pub u32);
impl RomanNumeral {
pub const fn new(value: u32) -> Result<Self, OutOfRangeError> {
if value <= 4_999_999 {
Ok(RomanNumeral(value))
} else {
Err(OutOfRangeError)
}
}
#[must_use]
pub const fn as_u32(self) -> u32 {
self.0
}
#[must_use]
#[cfg(feature = "std")]
pub fn to_uppercase(&self) -> String {
let mut out = String::new();
if self.0 == 0 {
out.push_str(&"N".to_string());
} else {
out.push_str(&Self::arabic_to_roman(self.0, true));
}
out
}
#[must_use]
#[cfg(feature = "std")]
pub fn to_lowercase(self) -> String {
let mut out = String::new();
if self.0 == 0 {
out.push_str(&"N".to_string());
} else {
out.push_str(&Self::arabic_to_roman(self.0, false));
}
out
}
fn repeat(total: u8, character: &str) -> String {
let mut count = total;
let mut out = String::new();
while count > 0 {
out.push_str(character);
count -= 1;
}
out
}
fn arabic_to_roman(num: u32, uppercase: bool) -> String {
let mut out: String = String::new();
if num != 0 {
for (index, basis) in NUMERI.iter().enumerate() {
let arabic = num / basis.arabic; if arabic > 0 {
let offset = if basis.arabic.to_string().contains("5") {
1
} else {
0
};
if num >= NUMERI[index - 1].arabic - NUMERI[index + offset].arabic {
if 4000 <= num && num < 10000 {
if uppercase {
out.push_str(&"IÌ…".to_string());
} else {
out.push_str(&"iÌ…".to_string());
}
} else {
if uppercase {
out.push_str(NUMERI[index + offset].u_latin);
} else {
out.push_str(NUMERI[index + offset].l_latin);
}
}
out.push_str(NUMERI[index - 1].u_latin);
out.push_str(&Self::arabic_to_roman(
(num - (NUMERI[index - 1].arabic - NUMERI[index + offset].arabic))
% basis.arabic,
uppercase,
));
} else {
if uppercase {
out.push_str(&Self::repeat(
u8::try_from(arabic).unwrap(),
basis.u_latin,
));
} else {
out.push_str(&Self::repeat(
u8::try_from(arabic).unwrap(),
basis.l_latin,
));
}
out.push_str(&Self::arabic_to_roman(
u32::try_from(
(isize::try_from(num).unwrap()
- isize::try_from(NUMERI[index - 1].arabic).unwrap())
.rem_euclid(isize::try_from(basis.arabic).unwrap()),
)
.unwrap(),
uppercase,
));
}
break;
}
}
}
out
}
}
#[cfg(feature = "std")]
impl fmt::Display for RomanNumeral {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.to_uppercase())
}
}
struct Arabic2RomanStruct<'a> {
arabic: u32,
u_latin: &'a str,
l_latin: &'a str,
}
static NUMERI: [Arabic2RomanStruct; 13] = [
Arabic2RomanStruct {
arabic: 1000000,
u_latin: "MÌ…",
l_latin: "mÌ…",
},
Arabic2RomanStruct {
arabic: 500000,
u_latin: "DÌ…",
l_latin: "dÌ…",
},
Arabic2RomanStruct {
arabic: 100000,
u_latin: "CÌ…",
l_latin: "cÌ…",
},
Arabic2RomanStruct {
arabic: 50000,
u_latin: "LÌ…",
l_latin: "lÌ…",
},
Arabic2RomanStruct {
arabic: 10000,
u_latin: "XÌ…",
l_latin: "xÌ…",
},
Arabic2RomanStruct {
arabic: 5000,
u_latin: "VÌ…",
l_latin: "uÌ…",
},
Arabic2RomanStruct {
arabic: 1000,
u_latin: "M",
l_latin: "m",
},
Arabic2RomanStruct {
arabic: 500,
u_latin: "D",
l_latin: "d",
},
Arabic2RomanStruct {
arabic: 100,
u_latin: "C",
l_latin: "c",
},
Arabic2RomanStruct {
arabic: 50,
u_latin: "L",
l_latin: "l",
},
Arabic2RomanStruct {
arabic: 10,
u_latin: "X",
l_latin: "x",
},
Arabic2RomanStruct {
arabic: 5,
u_latin: "V",
l_latin: "u",
},
Arabic2RomanStruct {
arabic: 1,
u_latin: "I",
l_latin: "i",
},
];
impl TryFrom<u8> for RomanNumeral {
type Error = OutOfRangeError;
fn try_from(value: u8) -> Result<Self, OutOfRangeError> {
Self::new(u32::from(value))
}
}
impl TryFrom<u16> for RomanNumeral {
type Error = OutOfRangeError;
fn try_from(value: u16) -> Result<Self, OutOfRangeError> {
Self::new(u32::from(value))
}
}
impl TryFrom<u32> for RomanNumeral {
type Error = OutOfRangeError;
fn try_from(value: u32) -> Result<Self, OutOfRangeError> {
Self::new(value)
}
}
impl TryFrom<u64> for RomanNumeral {
type Error = OutOfRangeError;
fn try_from(value: u64) -> Result<Self, OutOfRangeError> {
u32::try_from(value).map_or(Err(OutOfRangeError), Self::new)
}
}
impl TryFrom<u128> for RomanNumeral {
type Error = OutOfRangeError;
fn try_from(value: u128) -> Result<Self, OutOfRangeError> {
u32::try_from(value).map_or(Err(OutOfRangeError), Self::new)
}
}
impl TryFrom<usize> for RomanNumeral {
type Error = OutOfRangeError;
fn try_from(value: usize) -> Result<Self, OutOfRangeError> {
u32::try_from(value).map_or(Err(OutOfRangeError), Self::new)
}
}
impl TryFrom<i8> for RomanNumeral {
type Error = OutOfRangeError;
fn try_from(value: i8) -> Result<Self, OutOfRangeError> {
u32::try_from(value).map_or(Err(OutOfRangeError), Self::new)
}
}
impl TryFrom<i16> for RomanNumeral {
type Error = OutOfRangeError;
fn try_from(value: i16) -> Result<Self, OutOfRangeError> {
u32::try_from(value).map_or(Err(OutOfRangeError), Self::new)
}
}
impl TryFrom<i32> for RomanNumeral {
type Error = OutOfRangeError;
fn try_from(value: i32) -> Result<Self, OutOfRangeError> {
u32::try_from(value).map_or(Err(OutOfRangeError), Self::new)
}
}
impl TryFrom<i64> for RomanNumeral {
type Error = OutOfRangeError;
fn try_from(value: i64) -> Result<Self, OutOfRangeError> {
u32::try_from(value).map_or(Err(OutOfRangeError), Self::new)
}
}
impl TryFrom<i128> for RomanNumeral {
type Error = OutOfRangeError;
fn try_from(value: i128) -> Result<Self, OutOfRangeError> {
u32::try_from(value).map_or(Err(OutOfRangeError), Self::new)
}
}