use core::convert::TryFrom;
use fpdec_core::{i128_div_mod_floor, i128_magnitude, MAX_N_FRAC_DIGITS};
use crate::{normalize, Decimal, DecimalError};
fn f64_decode(f: f64) -> (u64, i16, i8) {
let bits = f.to_bits();
let sign_bit: u8 = (bits >> 63) as u8;
let biased_exp = ((bits >> 52) & 0x7ff) as i16;
assert_ne!(biased_exp, 0x7ff);
let fraction = bits & 0xfffffffffffff;
let (significand, exponent, sign) = if biased_exp == 0 {
(0, 0, 0)
} else {
(
fraction | 0x10000000000000, biased_exp - 1023 - 52, 1 - (sign_bit << 1) as i8, )
};
(significand, exponent, sign)
}
fn f32_decode(f: f32) -> (u64, i16, i8) {
let bits = f.to_bits();
let sign_bit: u8 = (bits >> 31) as u8;
let biased_exp = ((bits >> 23) & 0xff) as i16;
assert_ne!(biased_exp, 0xff);
let fraction = (bits & 0x7fffff) as u64;
let (significand, exponent, sign) = if biased_exp == 0 {
(0, 0, 0)
} else {
(
fraction | 0x800000, biased_exp - 127 - 23, 1 - (sign_bit << 1) as i8, )
};
(significand, exponent, sign)
}
const MAGN_I128_MAX: u8 = 38;
#[allow(clippy::integer_division)]
#[inline]
fn approx_rational(divident: i128, divisor: i128) -> (i128, u8) {
assert!(divisor > 0);
if divisor == 1 {
return (divident, 0);
}
if divident == 0 {
return (0, 0);
}
let mut n_frac_digits = 0_u8;
let (mut coeff, mut rem) = i128_div_mod_floor(divident, divisor);
let mut magn_coeff = i128_magnitude(coeff);
while rem != 0
&& n_frac_digits < MAX_N_FRAC_DIGITS
&& magn_coeff < MAGN_I128_MAX - 1
{
rem *= 10;
let quot = rem / divisor;
rem %= divisor;
n_frac_digits += 1;
magn_coeff += 1;
coeff = coeff * 10 + quot;
}
rem <<= 1;
if rem > divisor || rem == divisor && coeff < 0 {
coeff += 1;
}
normalize(&mut coeff, &mut n_frac_digits);
(coeff, n_frac_digits)
}
impl TryFrom<f32> for Decimal {
type Error = DecimalError;
fn try_from(f: f32) -> Result<Self, Self::Error> {
if f.is_infinite() {
return Err(DecimalError::InfiniteValue);
}
if f.is_nan() {
return Err(DecimalError::NotANumber);
}
let (significand, exponent, sign) = f32_decode(f);
if exponent < -126 {
Ok(Self::ZERO)
} else if exponent < 0 {
let numer = i128::from(sign) * i128::from(significand);
let denom = 1_i128 << ((-exponent) as usize);
let (coeff, n_frac_digits) = approx_rational(numer, denom);
Ok(Self {
coeff,
n_frac_digits,
})
} else {
let numer = i128::from(sign) * i128::from(significand);
let shift = 1_i128 << exponent as usize;
match numer.checked_mul(shift) {
Some(coeff) => Ok(Self {
coeff,
n_frac_digits: 0,
}),
None => Err(DecimalError::InternalOverflow),
}
}
}
}
impl TryFrom<f64> for Decimal {
type Error = DecimalError;
fn try_from(f: f64) -> Result<Self, Self::Error> {
if f.is_infinite() {
return Err(DecimalError::InfiniteValue);
}
if f.is_nan() {
return Err(DecimalError::NotANumber);
}
let (significand, exponent, sign) = f64_decode(f);
if exponent < -126 {
Ok(Self::ZERO)
} else if exponent < 0 {
let numer = i128::from(sign) * i128::from(significand);
let denom = 1_i128 << ((-exponent) as usize);
let (coeff, n_frac_digits) = approx_rational(numer, denom);
Ok(Self {
coeff,
n_frac_digits,
})
} else {
let numer = i128::from(sign) * i128::from(significand);
let shift = 1_i128 << exponent as usize;
match numer.checked_mul(shift) {
Some(coeff) => Ok(Self {
coeff,
n_frac_digits: 0,
}),
None => Err(DecimalError::InternalOverflow),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn check_from_float<T>(test_data: &[(T, i128, u8)])
where
T: Copy,
Decimal: TryFrom<T>,
{
for (val, coeff, n_frac_digits) in test_data {
match Decimal::try_from(*val) {
Err(_) => panic!("Mismatched test data: {}", coeff),
Ok(d) => {
assert_eq!(d.coefficient(), *coeff);
assert_eq!(d.n_frac_digits(), *n_frac_digits);
}
}
}
}
#[test]
fn test_decimal0_from_f32() {
let test_data = [
(i128::MIN as f32, i128::MIN, 0),
(-289.00, -289, 0),
(-2., -2, 0),
(0.0, 0, 0),
(5., 5, 0),
((i128::MAX / 2) as f32, i128::MAX / 2 + 1, 0),
];
check_from_float::<f32>(&test_data);
}
#[test]
fn test_decimal_from_f32() {
let test_data = [
(-289.5_f32, -2895, 1),
(-0.5005_f32, -500500023365020752, 18),
(37.000503_f32, 370005035400390625, 16),
];
check_from_float::<f32>(&test_data);
}
#[test]
fn test_decimal0_from_f64() {
let test_data = [
(i128::MIN as f64, i128::MIN, 0),
(-289.0, -289, 0),
(-2., -2, 0),
(0.0, 0, 0),
(5.000, 5, 0),
((i128::MAX / 2) as f64, i128::MAX / 2 + 1, 0),
];
check_from_float::<f64>(&test_data);
}
#[test]
fn test_decimal_from_f64() {
let test_data = [
(-28900.000000005_f64, -28900000000004998582881, 18),
(-5e-7, -5, 7),
(1.004e-127, 0, 0),
(1.0005, 1000499999999999945, 18),
(37.0005000033, 37000500003299997331, 18),
];
check_from_float::<f64>(&test_data);
}
#[test]
fn test_fail_overflow() {
let f = 5.839e38_f64;
let res = Decimal::try_from(f);
assert!(res.is_err());
let err = res.unwrap_err();
assert_eq!(err, DecimalError::InternalOverflow);
}
#[test]
fn test_fail_on_f32_infinite_value() {
for f in [f32::INFINITY, f32::NEG_INFINITY] {
let res = Decimal::try_from(f);
assert!(res.is_err());
let err = res.unwrap_err();
assert_eq!(err, DecimalError::InfiniteValue);
}
}
#[test]
fn test_fail_on_f64_infinite_value() {
for f in [f64::INFINITY, f64::NEG_INFINITY] {
let res = Decimal::try_from(f);
assert!(res.is_err());
let err = res.unwrap_err();
assert_eq!(err, DecimalError::InfiniteValue);
}
}
#[test]
fn test_fail_on_f32_nan() {
let f = f32::NAN;
let res = Decimal::try_from(f);
assert!(res.is_err());
let err = res.unwrap_err();
assert_eq!(err, DecimalError::NotANumber);
}
#[test]
fn test_fail_on_f64_nan() {
let f = f64::NAN;
let res = Decimal::try_from(f);
assert!(res.is_err());
let err = res.unwrap_err();
assert_eq!(err, DecimalError::NotANumber);
}
}