Skip to content

Commit

Permalink
dec: support TryFrom<Decimal<N>> for f32/f64
Browse files Browse the repository at this point in the history
  • Loading branch information
sploiselle committed Jun 1, 2021
1 parent e3e6f7e commit 3b91351
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 1 deletion.
3 changes: 2 additions & 1 deletion dec/CHANGELOG.md
Expand Up @@ -7,9 +7,10 @@ Versioning].

## Unreleased

- Support the following `Decimal` functions:
* Support the following `Decimal` functions:
- `round`
- `from_raw_parts`
* Implement `TryInto<Decimal<N>>` for `f32` and `f64`.

## 0.4.0 - 2021-05-20

Expand Down
77 changes: 77 additions & 0 deletions dec/src/decimal.rs
Expand Up @@ -542,6 +542,65 @@ impl<const N: usize> TryFrom<Decimal<N>> for isize {
}
}

macro_rules! decnum_tryinto_primitive_float {
($p:ty, $cx:expr, $d:expr) => {{
if $d.is_infinite() {
return Ok(if $d.is_negative() {
<$p>::NEG_INFINITY
} else {
<$p>::INFINITY
});
}
if $d.is_nan() {
return Ok(<$p>::NAN);
}

const TEN: $p = 10.0;
const DECDPUN_F: $p = decnumber_sys::DECDPUN as $p;

let mut e = $d.exponent() as $p;
let mut f: $p = 0.0;
for u in $d.coefficient_units() {
// `powi` gives wrong results on some input, whereas `powf` does not
f += <$p>::from(*u) * TEN.powf(e);
e += DECDPUN_F;
}
if $d.is_negative() {
f = -f;
}

// Value over- or underflows $p:
// - f.is_infinite() represents generic overflow
// - f.is_nan() can occur when multiplying a coefficient unit by a power
// of 10 that exceeds the primitive type's maximum exponent
// - (!$d.is_zero() && f == 0.0) represents underflow
if f.is_infinite() || f.is_nan() || (!$d.is_zero() && f == 0.0) {
let mut s = $cx.status();
s.set_invalid_operation();
$cx.set_status(s);
Err(TryFromDecimalError)
} else {
Ok(f)
}
}};
}

impl<const N: usize> TryFrom<Decimal<N>> for f32 {
type Error = TryFromDecimalError;
fn try_from(n: Decimal<N>) -> Result<f32, Self::Error> {
let mut cx = Context::<Decimal<N>>::default();
cx.try_into_f32(n)
}
}

impl<const N: usize> TryFrom<Decimal<N>> for f64 {
type Error = TryFromDecimalError;
fn try_from(n: Decimal<N>) -> Result<f64, Self::Error> {
let mut cx = Context::<Decimal<N>>::default();
cx.try_into_f64(n)
}
}

impl<const N: usize> From<u32> for Decimal<N> {
fn from(n: u32) -> Decimal<N> {
let mut d = Decimal::default();
Expand Down Expand Up @@ -1022,6 +1081,24 @@ impl<const N: usize> Context<Decimal<N>> {
decnum_tryinto_primitive_uint!(u128, self, 39, d)
}

/// Attempts to convert `d` to `f32` or fails if not possible.
///
/// Note that this function:
/// - Errors for values that over- or underflow `f32`, rather than returning
/// infinity or `0.0`, respectively.
/// - Returns a primitive infinity or NaN if `d` is an equivalent value.
pub fn try_into_f32(&mut self, d: Decimal<N>) -> Result<f32, TryFromDecimalError> {
decnum_tryinto_primitive_float!(f32, self, d)
}

/// Attempts to convert `d` to `f32` or fails if not possible.
///
/// Refer to the comments on [`Self::try_into_f32()`], which also apply to this
/// function.
pub fn try_into_f64(&mut self, d: Decimal<N>) -> Result<f64, TryFromDecimalError> {
decnum_tryinto_primitive_float!(f64, self, d)
}

/// Computes the digitwise logical inversion of `n`, storing the result in
/// `n`.
pub fn invert(&mut self, n: &mut Decimal<N>) {
Expand Down
96 changes: 96 additions & 0 deletions dec/tests/dec.rs
Expand Up @@ -1675,6 +1675,102 @@ fn test_decnum_tryinto_primitive() {
quantizable(9999, 99999999, false);
}

#[test]
fn test_decnum_tryinto_primitive_f32() {
const WIDTH: usize = 14;
fn inner(s: &str, e: Result<f32, ()>) {
let mut cx = Context::<Decimal<WIDTH>>::default();
let d = cx.parse(s).unwrap();
let r = cx.try_into_f32(d);
if e.is_err() {
assert!(r.is_err(), "expected {:?}, got {:?}", e, r)
} else {
let e = e.unwrap();
let r = r.unwrap();
if e.is_nan() {
assert!(r.is_nan())
} else {
assert_eq!(e, r)
}
}
}
inner("1.234", Ok(1.234));
inner("1234.567891234567", Ok(1234.5679));
inner(
"0.0000000000000001234567891234567",
Ok(0.00000000000000012345679),
);
inner(
"-0.0000000000000001234567891234567",
Ok(-0.00000000000000012345679),
);
inner("0.000", Ok(0.0));
inner("NaN", Ok(f32::NAN));
inner("-NaN", Ok(f32::NAN));
inner("Infinity", Ok(f32::INFINITY));
inner("-Infinity", Ok(f32::NEG_INFINITY));
inner("1000", Ok(1000.0));
inner(&f32::MAX.to_string(), Ok(f32::MAX));
inner(&f32::MIN.to_string(), Ok(f32::MIN));
inner(&f64::MAX.to_string(), Err(()));
inner(&f64::MIN.to_string(), Err(()));
inner("1e39", Err(()));
inner("1e-50", Err(()));
}

#[test]
fn test_decnum_tryinto_primitive_f64() {
const WIDTH: usize = 14;
fn inner(s: &str, f: Result<f64, ()>) {
let mut cx = Context::<Decimal<WIDTH>>::default();
let d = cx.parse(s).unwrap();
let r = cx.try_into_f64(d);

if f.is_err() {
assert!(r.is_err(), "expected {:?}, got {:?}", f, r)
} else {
let f = f.unwrap();
let r = r.unwrap();
if f.is_nan() {
assert!(f.is_nan())
} else {
assert_eq!(f, r)
}
}
}
inner("1.234", Ok(1.234));
inner(
"1234567890.12345678901234567891234567890",
Ok(1234567890.1234568),
);
inner(
"12345678912345678901.234567891234567890",
Ok(12345678912345680000.0),
);
inner(
".0000000000000000000000012345678912345678901234567891234567890",
Ok(0.000000000000000000000001234567891234568),
);
inner(
"1.2345678912345678901234567891234567890E-24",
Ok(0.000000000000000000000001234567891234568),
);
inner(
"-1.2345678912345678901234567891234567890E-24",
Ok(-0.000000000000000000000001234567891234568),
);
inner("1e39", Ok(1000000000000000000000000000000000000000.0));
inner("0.000", Ok(0.0));
inner(&f64::MAX.to_string(), Ok(f64::MAX));
inner(&f64::MIN.to_string(), Ok(f64::MIN));
inner("NaN", Ok(f64::NAN));
inner("-NaN", Ok(f64::NAN));
inner("Infinity", Ok(f64::INFINITY));
inner("-Infinity", Ok(f64::NEG_INFINITY));
inner("1e500", Err(()));
inner("1e-500", Err(()));
}

#[test]
fn test_decnum_coefficient() {
const WIDTH: usize = 14;
Expand Down

0 comments on commit 3b91351

Please sign in to comment.