Skip to content

Commit

Permalink
Add TryFrom impls to primitive types (#493)
Browse files Browse the repository at this point in the history
Fixes #492 

Co-authored-by: Manuel Bärenz <m.baerenz@sonnen.de>
Co-authored-by: Paul Mason <paul@paulmason.me>
  • Loading branch information
3 people committed Mar 18, 2022
1 parent 4b97ec4 commit 168fa67
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 32 deletions.
109 changes: 77 additions & 32 deletions src/decimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1694,8 +1694,84 @@ const fn flags(neg: bool, scale: u32) -> u32 {
(scale << SCALE_SHIFT) | ((neg as u32) << SIGN_SHIFT)
}

macro_rules! integer_docs {
( true ) => {
" by truncating and returning the integer component"
};
( false ) => {
""
};
}

// #[doc] attributes are formatted poorly with rustfmt so skip for now.
// See https://github.com/rust-lang/rustfmt/issues/5062 for more information.
#[rustfmt::skip]
macro_rules! impl_try_from_decimal {
($TInto:ty, $conversion_fn:path, $additional_docs:expr) => {
#[doc = concat!(
"Try to convert a `Decimal` to `",
stringify!($TInto),
"`",
$additional_docs,
".\n\nCan fail if the `Decimal` is out of range for `",
stringify!($TInto),
"`.",
)]
impl core::convert::TryFrom<Decimal> for $TInto {
type Error = crate::Error;

#[inline]
fn try_from(t: Decimal) -> Result<Self, Error> {
$conversion_fn(&t).ok_or_else(|| Error::ConversionTo(stringify!($TInto).to_string()))

This comment has been minimized.

Copy link
@Jules-Bertholet

Jules-Bertholet Mar 26, 2022

to_string requires std

}
}
};
}

impl_try_from_decimal!(f32, Decimal::to_f32, integer_docs!(false));
impl_try_from_decimal!(f64, Decimal::to_f64, integer_docs!(false));
impl_try_from_decimal!(isize, Decimal::to_isize, integer_docs!(true));
impl_try_from_decimal!(i8, Decimal::to_i8, integer_docs!(true));
impl_try_from_decimal!(i16, Decimal::to_i16, integer_docs!(true));
impl_try_from_decimal!(i32, Decimal::to_i32, integer_docs!(true));
impl_try_from_decimal!(i64, Decimal::to_i64, integer_docs!(true));
impl_try_from_decimal!(i128, Decimal::to_i128, integer_docs!(true));
impl_try_from_decimal!(usize, Decimal::to_usize, integer_docs!(true));
impl_try_from_decimal!(u8, Decimal::to_u8, integer_docs!(true));
impl_try_from_decimal!(u16, Decimal::to_u16, integer_docs!(true));
impl_try_from_decimal!(u32, Decimal::to_u32, integer_docs!(true));
impl_try_from_decimal!(u64, Decimal::to_u64, integer_docs!(true));
impl_try_from_decimal!(u128, Decimal::to_u128, integer_docs!(true));

// #[doc] attributes are formatted poorly with rustfmt so skip for now.
// See https://github.com/rust-lang/rustfmt/issues/5062 for more information.
#[rustfmt::skip]
macro_rules! impl_try_from_primitive {
($TFrom:ty, $conversion_fn:path) => {
#[doc = concat!(
"Try to convert a `",
stringify!($TFrom),
"` into a `Decimal`.\n\nCan fail if the value is out of range for `Decimal`."
)]
impl core::convert::TryFrom<$TFrom> for Decimal {
type Error = crate::Error;

#[inline]
fn try_from(t: $TFrom) -> Result<Self, Error> {
$conversion_fn(t).ok_or_else(|| Error::ConversionTo("Decimal".to_string()))
}
}
};
}

impl_try_from_primitive!(f32, Self::from_f32);
impl_try_from_primitive!(f64, Self::from_f64);

macro_rules! impl_from {
($T:ty, $from_ty:path) => {
///
/// Conversion to `Decimal`.
///
impl core::convert::From<$T> for Decimal {
#[inline]
fn from(t: $T) -> Self {
Expand All @@ -1704,6 +1780,7 @@ macro_rules! impl_from {
}
};
}

impl_from!(isize, FromPrimitive::from_isize);
impl_from!(i8, FromPrimitive::from_i8);
impl_from!(i16, FromPrimitive::from_i16);
Expand Down Expand Up @@ -2198,38 +2275,6 @@ impl ToPrimitive for Decimal {
}
}

impl core::convert::TryFrom<f32> for Decimal {
type Error = crate::Error;

fn try_from(value: f32) -> Result<Self, Error> {
Self::from_f32(value).ok_or_else(|| Error::from("Failed to convert to Decimal"))
}
}

impl core::convert::TryFrom<f64> for Decimal {
type Error = crate::Error;

fn try_from(value: f64) -> Result<Self, Error> {
Self::from_f64(value).ok_or_else(|| Error::from("Failed to convert to Decimal"))
}
}

impl core::convert::TryFrom<Decimal> for f32 {
type Error = crate::Error;

fn try_from(value: Decimal) -> Result<Self, Self::Error> {
Decimal::to_f32(&value).ok_or_else(|| Error::from("Failed to convert to f32"))
}
}

impl core::convert::TryFrom<Decimal> for f64 {
type Error = crate::Error;

fn try_from(value: Decimal) -> Result<Self, Self::Error> {
Decimal::to_f64(&value).ok_or_else(|| Error::from("Failed to convert to f64"))
}
}

impl fmt::Display for Decimal {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let (rep, additional) = crate::str::to_str_internal(self, false, f.precision());
Expand Down
4 changes: 4 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub enum Error {
LessThanMinimumPossibleValue,
Underflow,
ScaleExceedsMaximumPrecision(u32),
ConversionTo(String),
}

impl<S> From<S> for Error
Expand Down Expand Up @@ -50,6 +51,9 @@ impl fmt::Display for Error {
scale, MAX_PRECISION_U32
)
}
Self::ConversionTo(ref type_name) => {
write!(f, "Error while converting to {}", type_name)
}
}
}
}
7 changes: 7 additions & 0 deletions tests/decimal_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2864,6 +2864,13 @@ fn it_converts_from_f64_retaining_bits() {
}
}

#[test]
fn it_converts_to_integers() {
assert_eq!(i64::try_from(Decimal::ONE), Ok(1));
assert_eq!(i64::try_from(Decimal::MAX), Err(Error::ConversionTo("i64".to_string())));
assert_eq!(u128::try_from(Decimal::ONE_HUNDRED), Ok(100));
}

#[test]
fn it_handles_simple_underflow() {
// Issue #71
Expand Down

0 comments on commit 168fa67

Please sign in to comment.