use super::cmp::Magnitude;
use super::{ByteOrder, Number};
use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TryFromNumberError(());
impl fmt::Display for TryFromNumberError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
"out of range integral type conversion attempted".fmt(f)
}
}
impl std::error::Error for TryFromNumberError {}
#[inline(always)]
fn small_parts(n: &Number) -> Option<(bool, u128)> {
let m = Magnitude::of(n);
(m.bytes.len() <= 16).then(|| (m.negative, m.small()))
}
macro_rules! unsigned {
($($t:ident $new:ident),* $(,)?) => {$(
impl From<$t> for Number {
#[doc = concat!("Converts a `", stringify!($t), "` to a number, little-endian, as [`Number::", stringify!($new), "`] builds it.")]
#[inline]
fn from(value: $t) -> Number {
Number::$new(value, ByteOrder::LittleEndian)
}
}
impl TryFrom<&Number> for $t {
type Error = TryFromNumberError;
#[doc = concat!("Converts a number to a `", stringify!($t), "`, or fails when the number is negative or too large for it.")]
#[inline]
fn try_from(n: &Number) -> Result<$t, TryFromNumberError> {
match small_parts(n) {
Some((false, m)) => <$t>::try_from(m).map_err(|_| TryFromNumberError(())),
_ => Err(TryFromNumberError(())),
}
}
}
impl TryFrom<Number> for $t {
type Error = TryFromNumberError;
#[doc = concat!("Converts a number to a `", stringify!($t), "`, as from a borrowed number.")]
#[inline]
fn try_from(n: Number) -> Result<$t, TryFromNumberError> {
<$t>::try_from(&n)
}
}
)*};
}
macro_rules! signed {
($($t:ident $u:ident $new:ident),* $(,)?) => {$(
impl From<$t> for Number {
#[doc = concat!("Converts an `", stringify!($t), "` to a number, little-endian, as [`Number::", stringify!($new), "`] builds it.")]
#[inline]
fn from(value: $t) -> Number {
Number::$new(value, ByteOrder::LittleEndian)
}
}
impl TryFrom<&Number> for $t {
type Error = TryFromNumberError;
#[doc = concat!("Converts a number to an `", stringify!($t), "`, or fails when it is out of the type's range.")]
#[inline]
fn try_from(n: &Number) -> Result<$t, TryFromNumberError> {
let Some((negative, m)) = small_parts(n) else {
return Err(TryFromNumberError(()));
};
let m = <$u>::try_from(m).map_err(|_| TryFromNumberError(()))?;
if negative {
if m > <$t>::MIN.unsigned_abs() {
return Err(TryFromNumberError(()));
}
Ok((m as $t).wrapping_neg())
} else {
<$t>::try_from(m).map_err(|_| TryFromNumberError(()))
}
}
}
impl TryFrom<Number> for $t {
type Error = TryFromNumberError;
#[doc = concat!("Converts a number to an `", stringify!($t), "`, as from a borrowed number.")]
#[inline]
fn try_from(n: Number) -> Result<$t, TryFromNumberError> {
<$t>::try_from(&n)
}
}
)*};
}
unsigned!(
u8 new_from_u8,
u16 new_from_u16,
u32 new_from_u32,
u64 new_from_u64,
u128 new_from_u128,
usize new_from_usize,
);
signed!(
i8 u8 new_from_i8,
i16 u16 new_from_i16,
i32 u32 new_from_i32,
i64 u64 new_from_i64,
i128 u128 new_from_i128,
isize usize new_from_isize,
);
#[inline(always)]
fn top_bits(m: Magnitude) -> (u64, u64) {
let v = m.bytes;
let n = v.len();
debug_assert!(n > 16);
let (high, ninth) = if m.big_endian {
(
u64::from_be_bytes(v[..8].try_into().unwrap_or_default()),
v[8],
)
} else {
(
u64::from_le_bytes(v[n - 8..].try_into().unwrap_or_default()),
v[n - 9],
)
};
let lz = high.leading_zeros();
debug_assert!(lz < 8);
let bits = 8 * n as u64 - u64::from(lz);
(bits, (high << lz) | (u64::from(ninth) >> (8 - lz)))
}
#[cold]
fn sticky(m: Magnitude, bits: u64) -> bool {
let low = bits - 64;
let (whole, part) = ((low / 8) as usize, (low % 8) as u32);
let v = m.bytes;
let n = v.len();
let (bytes, partial) = if m.big_endian {
(&v[n - whole..], v[n - whole - 1])
} else {
(&v[..whole], v[whole])
};
bytes.iter().any(|&b| b != 0) || partial & ((1u8 << part) - 1) != 0
}
impl Number {
pub fn to_f64(&self) -> f64 {
let m = Magnitude::of(self);
if m.bytes.len() <= 16 {
let magnitude = m.small() as f64;
return if m.negative { -magnitude } else { magnitude };
}
let (bits, top) = top_bits(m);
let magnitude = if bits > 1024 {
f64::INFINITY
} else {
let halfway = top & 0x7FF == 0x400;
let top = top | u64::from(halfway && sticky(m, bits));
let scale = f64::from_bits((bits - 64 + 1023) << 52);
top as f64 * scale
};
if m.negative { -magnitude } else { magnitude }
}
pub fn to_f32(&self) -> f32 {
let m = Magnitude::of(self);
let magnitude = if m.bytes.len() <= 16 {
m.small() as f32
} else {
f32::INFINITY
};
if m.negative { -magnitude } else { magnitude }
}
}