use std::cmp::Ordering;
use std::fmt::Write as _;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Decimal {
pub negative: bool,
pub digits: String,
}
impl Decimal {
#[must_use]
pub fn parse(text: &str) -> Option<Self> {
let text = text.trim();
let (negative, body) = text
.strip_prefix('-')
.map_or((false, text), |rest| (true, rest));
let lower = body.to_ascii_lowercase();
let (radix, digits) = [("0x", 16), ("0o", 8), ("0b", 2)]
.into_iter()
.find_map(|(prefix, radix)| lower.strip_prefix(prefix).map(|rest| (radix, rest)))
.unwrap_or((10, lower.as_str()));
if digits.is_empty() || (radix != 10 && negative) {
return None;
}
let mut limbs: Vec<u64> = vec![0];
for ch in digits.chars() {
let digit = u64::from(ch.to_digit(radix)?);
let mut carry = digit;
for limb in &mut limbs {
let value = *limb * u64::from(radix) + carry;
*limb = value % 1_000_000_000;
carry = value / 1_000_000_000;
}
while carry > 0 {
limbs.push(carry % 1_000_000_000);
carry /= 1_000_000_000;
}
}
let mut out = String::new();
for (index, limb) in limbs.iter().rev().enumerate() {
if index == 0 {
out.push_str(&limb.to_string());
} else {
let _ = write!(out, "{limb:09}");
}
}
let zero = out == "0";
Some(Self {
negative: negative && !zero,
digits: out,
})
}
#[must_use]
pub fn from_i128(value: i128) -> Self {
Self {
negative: value < 0,
digits: value.unsigned_abs().to_string(),
}
}
#[must_use]
pub fn from_u128(value: u128) -> Self {
Self {
negative: false,
digits: value.to_string(),
}
}
#[must_use]
pub fn negate(mut self) -> Self {
if self.digits != "0" {
self.negative = !self.negative;
}
self
}
#[must_use]
pub const fn is_negative(&self) -> bool {
self.negative
}
#[must_use]
pub fn to_i128(&self) -> Option<i128> {
let text = self.to_string();
text.parse().ok()
}
}
impl std::fmt::Display for Decimal {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.negative {
formatter.write_str("-")?;
}
formatter.write_str(&self.digits)
}
}
fn magnitude(left: &str, right: &str) -> Ordering {
left.len().cmp(&right.len()).then_with(|| left.cmp(right))
}
impl Ord for Decimal {
fn cmp(&self, other: &Self) -> Ordering {
match (self.negative, other.negative) {
(false, true) => Ordering::Greater,
(true, false) => Ordering::Less,
(false, false) => magnitude(&self.digits, &other.digits),
(true, true) => magnitude(&other.digits, &self.digits),
}
}
}
impl PartialOrd for Decimal {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}