use std::fmt; use crate::{error, Error, Integer}; /// A structured field value [decimal]. /// /// Decimals have 12 digits of integer precision and 3 digits of fractional precision. /// /// [decimal]: #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Decimal(Integer); impl Decimal { /// The minimum value for a parsed or serialized decimal: `-999_999_999_999.999`. pub const MIN: Self = Self::from_integer_scaled_1000(Integer::MIN); /// The maximum value for a parsed or serialized decimal: `999_999_999_999.999`. pub const MAX: Self = Self::from_integer_scaled_1000(Integer::MAX); /// `0.0`. pub const ZERO: Self = Self(Integer::ZERO); /// Returns the decimal as an integer multiplied by 1000. /// /// The conversion is guaranteed to be precise. /// /// # Example /// /// ``` /// let decimal = sfv::Decimal::try_from(1.234).unwrap(); /// assert_eq!(i64::try_from(decimal.as_integer_scaled_1000()).unwrap(), 1234); /// ``` #[must_use] pub fn as_integer_scaled_1000(&self) -> Integer { self.0 } /// Creates a decimal from an integer multiplied by 1000. /// /// The conversion is guaranteed to be precise. /// /// # Example /// /// ``` /// let decimal = sfv::Decimal::from_integer_scaled_1000(sfv::integer(1234)); /// #[allow(clippy::float_cmp)] /// assert_eq!(f64::try_from(decimal).unwrap(), 1.234); /// ``` #[must_use] pub const fn from_integer_scaled_1000(v: Integer) -> Self { Self(v) } } impl fmt::Display for Decimal { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let v = i64::from(self.as_integer_scaled_1000()); if v == 0 { return f.write_str("0.0"); } let sign = if v < 0 { "-" } else { "" }; let v = v.abs(); let i_part = v / 1000; let f_part = v % 1000; if f_part % 100 == 0 { write!(f, "{}{}.{}", sign, i_part, f_part / 100) } else if f_part % 10 == 0 { write!(f, "{}{}.{:02}", sign, i_part, f_part / 10) } else { write!(f, "{sign}{i_part}.{f_part:03}") } } } impl From for Decimal { fn from(v: i8) -> Decimal { Self(Integer::from(i16::from(v) * 1000)) } } impl From for Decimal { fn from(v: i16) -> Decimal { Self(Integer::from(i32::from(v) * 1000)) } } impl From for Decimal { fn from(v: i32) -> Decimal { Self(Integer::try_from(i64::from(v) * 1000).unwrap()) } } macro_rules! impl_try_from_integer { ($($from: ty,)+) => { $( impl TryFrom<$from> for Decimal { type Error = Error; fn try_from(v: $from) -> Result { match v.checked_mul(1000) { None => Err(error::Repr::OutOfRange.into()), Some(v) => Integer::try_from(v).map(Decimal), } } } )+ } } impl_try_from_integer! { i64, i128, isize, u64, u128, usize, } impl From for Decimal { fn from(v: u8) -> Decimal { Self(Integer::from(u16::from(v) * 1000)) } } impl From for Decimal { fn from(v: u16) -> Decimal { Self(Integer::from(u32::from(v) * 1000)) } } impl From for Decimal { fn from(v: u32) -> Decimal { Self(Integer::try_from(u64::from(v) * 1000).unwrap()) } } impl From for f64 { #[allow(clippy::cast_precision_loss)] fn from(v: Decimal) -> Self { let v = i64::from(v.as_integer_scaled_1000()); (v as f64) / 1000.0 } } impl TryFrom for Decimal { type Error = Error; fn try_from(v: f32) -> Result { Self::try_from(f64::from(v)) } } impl TryFrom for Decimal { type Error = Error; fn try_from(v: f64) -> Result { if v.is_nan() { return Err(error::Repr::NaN.into()); } let v = (v * 1000.0).round_ties_even(); // Only excessively clever options exist for this conversion, so use "as" // Note that this relies on saturating casts for values > i64::MAX // See https://github.com/rust-lang/rust/issues/10184 #[allow(clippy::cast_possible_truncation)] Integer::try_from(v as i64).map(Decimal) } } impl TryFrom for Decimal { type Error = Error; fn try_from(v: Integer) -> Result { i64::from(v).try_into() } }