use rust_decimal::Error as DecimalLibraryError;
use std::{
convert::{TryFrom, TryInto},
fmt::{self, Debug, Display},
str::FromStr,
};
#[derive(Debug)]
pub enum DecimalError {
ExcessivePrecision,
InvalidPrecision,
DecimalError(DecimalLibraryError),
}
impl fmt::Display for DecimalError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
DecimalError::ExcessivePrecision => {
write!(f, "Decimal exceeds maximum fractional digits")
}
DecimalError::InvalidPrecision => {
write!(f, "Decimal is using an invalid precision must be 0 or 18")
}
DecimalError::DecimalError(v) => {
write!(f, "{:?}", v)
}
}
}
}
impl std::error::Error for DecimalError {}
impl From<DecimalLibraryError> for DecimalError {
fn from(error: DecimalLibraryError) -> Self {
DecimalError::DecimalError(error)
}
}
pub const PRECISION: u32 = 18;
pub const FRACTIONAL_DIGITS_MAX: u64 = 9_999_999_999_999_999_999;
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
pub struct Decimal(rust_decimal::Decimal);
impl Decimal {
pub fn new(integral_digits: i64, fractional_digits: u64) -> Result<Self, DecimalError> {
if fractional_digits > FRACTIONAL_DIGITS_MAX {
return Err(DecimalError::ExcessivePrecision);
}
let integral_digits: rust_decimal::Decimal = integral_digits.into();
let fractional_digits: rust_decimal::Decimal = fractional_digits.into();
let precision_exp: rust_decimal::Decimal = 10u64.pow(PRECISION).into();
let mut combined_decimal = (integral_digits * precision_exp) + fractional_digits;
combined_decimal.set_scale(PRECISION)?;
Ok(Decimal(combined_decimal))
}
}
impl Debug for Decimal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}
impl Display for Decimal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for Decimal {
type Err = DecimalError;
fn from_str(s: &str) -> Result<Self, DecimalError> {
s.parse::<rust_decimal::Decimal>()?.try_into()
}
}
impl TryFrom<rust_decimal::Decimal> for Decimal {
type Error = DecimalError;
fn try_from(mut decimal_value: rust_decimal::Decimal) -> Result<Self, DecimalError> {
match decimal_value.scale() {
0 => {
let exp: rust_decimal::Decimal = 10u64.pow(PRECISION).into();
decimal_value *= exp;
decimal_value.set_scale(PRECISION)?;
}
PRECISION => (),
_other => return Err(DecimalError::InvalidPrecision),
}
Ok(Decimal(decimal_value))
}
}
macro_rules! impl_from_primitive_int_for_decimal {
($($int:ty),+) => {
$(impl From<$int> for Decimal {
fn from(num: $int) -> Decimal {
#[allow(trivial_numeric_casts)]
Decimal::new(num as i64, 0).unwrap()
}
})+
};
}
impl_from_primitive_int_for_decimal!(i8, i16, i32, i64, isize);
impl_from_primitive_int_for_decimal!(u8, u16, u32, u64, usize);
#[cfg(test)]
mod tests {
use super::Decimal;
#[test]
fn string_serialization_test() {
let num = Decimal::from(-1i8);
assert_eq!(num.to_string(), "-1.000000000000000000")
}
}