use alloc::format;
use alloc::string::String;
use alloc::string::ToString;
use core::fmt;
use core::str::FromStr;
use crate::error::Error;
use crate::event::Atom;
use crate::ext::known::{WellKnown, impl_well_known, invalid};
use crate::ext::{BigInt, Extension, Number};
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Decimal(String);
impl Decimal {
pub fn new<S: Into<String>>(value: S) -> Result<Decimal, Error> {
let value = value.into();
if is_valid_decimal(&value) {
Ok(Decimal(value))
} else {
Err(invalid("invalid decimal"))
}
}
pub fn from_parts(mantissa: &BigInt, exponent: i64) -> Decimal {
let mut digits = mantissa.to_string();
let negative = digits.starts_with('-');
if negative {
digits.remove(0);
}
let mut rv = String::new();
if negative {
rv.push('-');
}
let scale = exponent.unsigned_abs();
if exponent < 0 && scale <= digits.len() as u64 + 20 {
let scale = scale as usize;
if digits.len() > scale {
rv.push_str(&digits[..digits.len() - scale]);
rv.push('.');
rv.push_str(&digits[digits.len() - scale..]);
} else {
rv.push_str("0.");
rv.extend(core::iter::repeat_n('0', scale - digits.len()));
rv.push_str(&digits);
}
} else {
rv.push_str(&digits);
if exponent != 0 {
rv.push('e');
rv.push_str(&exponent.to_string());
}
}
Decimal(rv)
}
pub fn to_parts(&self) -> (BigInt, i64) {
let s = self.0.as_str();
let (mantissa, exponent) = match s.find(['e', 'E']) {
Some(idx) => (&s[..idx], &s[idx + 1..]),
None => (s, "0"),
};
let exponent: i64 =
exponent
.trim_start_matches('+')
.parse()
.unwrap_or(if exponent.starts_with('-') {
i64::MIN
} else {
i64::MAX
});
let (digits, scale) = match mantissa.split_once('.') {
Some((int, frac)) => (format!("{}{}", int, frac), frac.len() as i64),
None => (mantissa.to_string(), 0),
};
let mantissa: BigInt = digits.parse().unwrap();
(mantissa, exponent.saturating_sub(scale))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_string(self) -> String {
self.0
}
}
fn is_valid_decimal(s: &str) -> bool {
crate::ext::number::is_json_number(s)
}
impl fmt::Display for Decimal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl fmt::Debug for Decimal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Decimal({})", self.0)
}
}
impl FromStr for Decimal {
type Err = Error;
fn from_str(s: &str) -> Result<Decimal, Error> {
Decimal::new(s)
}
}
impl From<BigInt> for Decimal {
fn from(value: BigInt) -> Decimal {
Decimal(value.to_string())
}
}
impl From<i64> for Decimal {
fn from(value: i64) -> Decimal {
Decimal(value.to_string())
}
}
impl From<u64> for Decimal {
fn from(value: u64) -> Decimal {
Decimal(value.to_string())
}
}
impl TryFrom<f64> for Decimal {
type Error = Error;
fn try_from(value: f64) -> Result<Decimal, Error> {
if value.is_finite() {
Decimal::new(format!("{:?}", value))
} else {
Err(invalid("decimals cannot be infinite or NaN"))
}
}
}
impl TryFrom<f32> for Decimal {
type Error = Error;
fn try_from(value: f32) -> Result<Decimal, Error> {
if value.is_finite() {
Decimal::new(format!("{:?}", value))
} else {
Err(invalid("decimals cannot be infinite or NaN"))
}
}
}
impl Extension for Decimal {
fn name(&self) -> &str {
"decimal"
}
fn fallback(&self) -> Atom<'_> {
Atom::Str(self.0.as_str().into())
}
}
impl WellKnown for Decimal {
const EXPECTING: &'static str = "decimal";
fn from_atom(atom: &Atom) -> Result<Option<Decimal>, Error> {
Ok(Some(match *atom {
Atom::Ext(ref ext) => {
if let Some(value) = ext.downcast_ref::<Decimal>() {
value.clone()
} else if let Some(value) = ext.downcast_value_ref::<Number>() {
Decimal(value.as_str().to_string())
} else if let Some(value) = BigInt::from_atom(atom)? {
Decimal::from(value)
} else {
return Ok(None);
}
}
Atom::Str(ref value) => value.parse()?,
Atom::U64(value) => Decimal::from(value),
Atom::I64(value) => Decimal::from(value),
Atom::F64(value) => Decimal::try_from(value)?,
Atom::F32(value) => Decimal::try_from(value)?,
_ => return Ok(None),
}))
}
}
impl_well_known!(Decimal);
#[test]
fn test_decimal() {
for valid in ["0", "-0", "1.5", "-12.50", "1e5", "1E+5", "1.5e-7", "0.001"] {
assert_eq!(valid.parse::<Decimal>().unwrap().as_str(), valid);
}
for invalid in [
"", "-", "01", "1.", ".5", "+1", "1e", "1e+", "NaN", "1.5.5", " 1",
] {
assert!(invalid.parse::<Decimal>().is_err(), "{}", invalid);
}
let parts = |s: &str| {
let (mantissa, exponent) = s.parse::<Decimal>().unwrap().to_parts();
(mantissa.to_string(), exponent)
};
assert_eq!(parts("12.50"), ("1250".into(), -2));
assert_eq!(parts("-0.001"), ("-1".into(), -3));
assert_eq!(parts("1.5e10"), ("15".into(), 9));
assert_eq!(parts("7"), ("7".into(), 0));
let from_parts = |m: i64, e: i64| Decimal::from_parts(&BigInt::from(m), e).into_string();
assert_eq!(from_parts(1250, -2), "12.50");
assert_eq!(from_parts(-1, -3), "-0.001");
assert_eq!(from_parts(15, 9), "15e9");
assert_eq!(from_parts(7, 0), "7");
assert_eq!(from_parts(1, -40), "1e-40");
assert_eq!(Decimal::try_from(0.1).unwrap().as_str(), "0.1");
assert_eq!(Decimal::try_from(1e100).unwrap().as_str(), "1e100");
assert!(Decimal::try_from(f64::NAN).is_err());
}