use std::{
cmp::Ordering,
fmt::{Debug, Display, Write},
str::FromStr,
};
use smallstr::SmallString;
use crate::{
checked_pow10, debug_decimal, display_decimal, parse_decimal, u256::I256, Fixed, OutOfRange,
ParseDecimalError,
};
#[derive(Copy, Clone, Default)]
#[cfg_attr(feature = "size_of", derive(size_of::SizeOf))]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, rkyv::CheckBytes)
)]
pub struct DynamicDecimal {
pub sig: i128,
pub exponent: u8,
}
impl DynamicDecimal {
pub const MAX: Self = DynamicDecimal::new(i128::MAX, 0);
pub const MIN: Self = DynamicDecimal::new(i128::MIN, 0);
pub const ZERO: Self = DynamicDecimal::new(0, 0);
pub const ONE: Self = DynamicDecimal::new(1, 0);
const fn new(sig: i128, exponent: u8) -> Self {
Self { sig, exponent }
}
}
impl<const P: usize, const S: usize> From<Fixed<P, S>> for DynamicDecimal {
fn from(value: Fixed<P, S>) -> Self {
Self {
sig: value.0,
exponent: S as u8,
}
}
}
impl<const P: usize, const S: usize> TryFrom<DynamicDecimal> for Fixed<P, S> {
type Error = OutOfRange;
fn try_from(value: DynamicDecimal) -> Result<Self, Self::Error> {
Self::try_new_with_exponent(value.sig, S as i32 - value.exponent as i32).ok_or(OutOfRange)
}
}
impl Debug for DynamicDecimal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
debug_decimal(self.sig, self.exponent as usize, f)
}
}
impl Display for DynamicDecimal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
display_decimal(self.sig, self.exponent as usize, f)
}
}
impl FromStr for DynamicDecimal {
type Err = ParseDecimalError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (sig, exponent) = parse_decimal(s, 0)?;
match (sig, exponent) {
(0, _) => Ok(Self::ZERO),
(_, 1..) => {
let sig = checked_pow10(exponent.cast_unsigned())
.and_then(|m| m.checked_mul(sig))
.ok_or(ParseDecimalError::OutOfRange)?;
Ok(Self { sig, exponent: 0 })
}
(_, 0) => Ok(Self { sig, exponent: 0 }),
(_, -255..0) => Ok(Self {
sig,
exponent: (-exponent) as u8,
}),
(_, ..-255) => {
Ok(Self::ZERO)
}
}
}
}
impl TryFrom<u128> for DynamicDecimal {
type Error = OutOfRange;
fn try_from(value: u128) -> Result<Self, Self::Error> {
Ok(Self {
sig: value.try_into().map_err(|_| OutOfRange)?,
exponent: 0,
})
}
}
macro_rules! from_int {
($type_name:ty) => {
impl From<$type_name> for DynamicDecimal {
fn from(value: $type_name) -> Self {
Self {
sig: value as i128,
exponent: 0,
}
}
}
};
}
from_int!(i128);
from_int!(i64);
from_int!(i32);
from_int!(i16);
from_int!(i8);
from_int!(isize);
from_int!(u64);
from_int!(u32);
from_int!(u16);
from_int!(u8);
from_int!(usize);
impl From<DynamicDecimal> for i128 {
fn from(value: DynamicDecimal) -> Self {
checked_pow10(value.exponent.into()).map_or(0, |divisor| value.sig / divisor)
}
}
macro_rules! try_to_signed_int {
($type_name:ty) => {
impl TryFrom<DynamicDecimal> for $type_name {
type Error = OutOfRange;
fn try_from(value: DynamicDecimal) -> Result<Self, Self::Error> {
match checked_pow10(value.exponent.into()) {
Some(divisor) => (value.sig / divisor).try_into().map_err(|_| OutOfRange),
None => Ok(0),
}
}
}
};
}
try_to_signed_int!(i64);
try_to_signed_int!(i32);
try_to_signed_int!(i16);
try_to_signed_int!(i8);
try_to_signed_int!(isize);
macro_rules! try_to_unsigned_int {
($type_name:ty) => {
impl TryFrom<DynamicDecimal> for $type_name {
type Error = OutOfRange;
fn try_from(value: DynamicDecimal) -> Result<Self, Self::Error> {
match checked_pow10(value.exponent.into()) {
Some(divisor) => (value.sig / divisor).try_into().map_err(|_| OutOfRange),
None => Ok(0),
}
}
}
};
}
try_to_unsigned_int!(u128);
try_to_unsigned_int!(u64);
try_to_unsigned_int!(u32);
try_to_unsigned_int!(u16);
try_to_unsigned_int!(u8);
try_to_unsigned_int!(usize);
impl TryFrom<f64> for DynamicDecimal {
type Error = OutOfRange;
fn try_from(value: f64) -> Result<Self, Self::Error> {
let mut buf = SmallString::<[u8; 64]>::new();
write!(&mut buf, "{value:.15e}").unwrap();
buf.parse().map_err(|_| OutOfRange)
}
}
impl PartialEq for DynamicDecimal {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for DynamicDecimal {}
impl PartialOrd for DynamicDecimal {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for DynamicDecimal {
fn cmp(&self, other: &Self) -> Ordering {
match self.exponent.cmp(&other.exponent) {
Ordering::Less => {
if let Some(multiplier) =
checked_pow10(other.exponent as u32 - self.exponent as u32)
{
I256::from_product(self.sig, multiplier).cmp(&I256::from(other.sig))
} else {
match self.sig.cmp(&0) {
Ordering::Equal => 0.cmp(&other.sig),
ordering => ordering,
}
}
}
Ordering::Equal => self.sig.cmp(&other.sig),
Ordering::Greater => {
if let Some(multiplier) =
checked_pow10(self.exponent as u32 - other.exponent as u32)
{
I256::from(self.sig).cmp(&I256::from_product(other.sig, multiplier))
} else {
match other.sig.cmp(&0) {
Ordering::Equal => self.sig.cmp(&0),
ordering => ordering.reverse(),
}
}
}
}
}
}
#[cfg(test)]
mod test {
use crate::{DynamicDecimal, OutOfRange, ParseDecimalError};
#[test]
fn eq() {
assert_eq!(DynamicDecimal::new(123, 2), DynamicDecimal::new(1230, 3));
assert_eq!(DynamicDecimal::new(1230, 3), DynamicDecimal::new(123, 2));
assert_eq!(DynamicDecimal::new(123, 2), DynamicDecimal::new(123, 2));
assert_ne!(DynamicDecimal::new(123, 2), DynamicDecimal::new(123, 3));
}
#[test]
fn compare() {
type DD = DynamicDecimal;
fn check_comparisons(dx: DD, dy: DD, x: i128, y: i128) {
assert_eq!(dx == dy, x == y);
assert_eq!(dx != dy, x != y);
assert_eq!(dx > dy, x > y);
assert_eq!(dx >= dy, x >= y);
assert_eq!(dx < dy, x < y);
assert_eq!(dx <= dy, x <= y);
}
for x in -999..=999 {
let fx = DD::new(x, 1);
for y in -999..=999 {
check_comparisons(fx, DD::new(y, 0), x, y * 10);
check_comparisons(fx, DD::new(y, 1), x, y);
check_comparisons(fx, DD::new(y, 2), x * 10, y);
}
}
check_comparisons(DD::new(0, 40), DD::new(0, 0), 0, 0);
check_comparisons(DD::new(0, 0), DD::new(0, 40), 0, 0);
check_comparisons(DD::new(1, 40), DD::new(1, 0), 0, 1);
check_comparisons(DD::new(1, 40), DD::new(-1, 0), 1, 0);
check_comparisons(DD::new(-1, 40), DD::new(1, 0), 0, 1);
check_comparisons(DD::new(-1, 40), DD::new(-1, 0), 1, 0);
check_comparisons(DD::new(1, 0), DD::new(1, 40), 1, 0);
check_comparisons(DD::new(1, 0), DD::new(-1, 40), 1, 0);
check_comparisons(DD::new(-1, 0), DD::new(1, 40), 0, 1);
check_comparisons(DD::new(-1, 0), DD::new(-1, 40), 0, 1);
}
#[test]
fn from_str() {
for (s, expect) in [
("0", Ok("0")),
("0.", Ok("0")),
(".0", Ok("0")),
("-0", Ok("0")),
("+0", Ok("0")),
("--0", Err(ParseDecimalError::SyntaxError)),
("-+0", Err(ParseDecimalError::SyntaxError)),
("0x", Err(ParseDecimalError::SyntaxError)),
("0e5x", Err(ParseDecimalError::SyntaxError)),
("1.23", Ok("1.23")),
("-1.23", Ok("-1.23")),
("+1.23", Ok("1.23")),
("99999999", Ok("99999999")),
("999999999", Ok("999999999")),
("999999999E-1", Ok("99999999.9")),
("9999999999e-1", Ok("999999999.9")),
("9999999999E-2", Ok("99999999.99")),
("99999999999e-2", Ok("999999999.99")),
("99999999999e-3", Ok("99999999.999")),
("99999999991e-3", Ok("99999999.991")),
(
"111111111111111111111111111111111111111111e-34",
Ok("11111111.1111111111111111111111111111111"),
),
(
"1.23456788901234567890123456789012345678890123456",
Ok("1.23456788901234567890123456789012345678"),
),
("1e999999999999999", Err(ParseDecimalError::OutOfRange)),
("0e999999999999999", Ok("0")),
("1e-999999999999999", Ok("0")),
(
"111111111111111111111111111111111111111111e2147483644",
Err(ParseDecimalError::OutOfRange),
),
(
".1111111111111111111111111111111111111111e-2147483648",
Ok("0"),
),
("123e5", Ok("12300000")),
("123E4", Ok("1230000")),
("123e3", Ok("123000")),
("123e2", Ok("12300")),
("123e1", Ok("1230")),
("123e0", Ok("123")),
("123e-1", Ok("12.3")),
("123e-2", Ok("1.23")),
(".123", Ok("0.123")),
(".124", Ok("0.124")),
(".125", Ok("0.125")),
(".126", Ok("0.126")),
(".133", Ok("0.133")),
(".134", Ok("0.134")),
(".135", Ok("0.135")),
(".136", Ok("0.136")),
("1e38", Ok("100000000000000000000000000000000000000")),
("1e39", Err(ParseDecimalError::OutOfRange)),
("1e-255", Ok("0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001")),
("1e-256", Ok("0")),
] {
println!("{s}: {:?}", s.parse::<DynamicDecimal>());
assert_eq!(
s.parse::<DynamicDecimal>().map(|d| d.to_string()),
expect.map(|d| d.to_string())
);
}
}
#[test]
fn to_integer() {
for x in -9999..=9999 {
let f = DynamicDecimal::new(x, 1);
assert_eq!(i128::from(f), x / 10);
assert_eq!(i64::try_from(f).unwrap(), (x / 10) as i64);
assert_eq!(i32::try_from(f).unwrap(), (x / 10) as i32);
assert_eq!(i16::try_from(f).unwrap(), (x / 10) as i16);
assert_eq!(
i8::try_from(f).ok(),
(-1289..=1279).contains(&x).then_some((x / 10) as i8)
);
assert_eq!(
u128::try_from(f).ok(),
(x > -10).then_some((x / 10) as u128)
);
assert_eq!(u64::try_from(f).ok(), (x > -10).then_some((x / 10) as u64));
assert_eq!(u32::try_from(f).ok(), (x > -10).then_some((x / 10) as u32));
assert_eq!(u16::try_from(f).ok(), (x > -10).then_some((x / 10) as u16));
assert_eq!(
u8::try_from(f).ok(),
(-9..=2559).contains(&x).then_some((x / 10) as u8)
);
}
assert_eq!(i128::from(DynamicDecimal::new(1, 40)), 0);
assert_eq!(i128::from(DynamicDecimal::new(i128::MAX, 0)), i128::MAX);
assert_eq!(i64::try_from(DynamicDecimal::new(1, 40)), Ok(0));
assert_eq!(
i64::try_from(DynamicDecimal::new(i128::MAX, 0)),
Err(OutOfRange)
);
assert_eq!(
i64::try_from(DynamicDecimal::new(i128::MIN, 0)),
Err(OutOfRange)
);
}
}