Skip to main content

a/math/numbers/
convert.rs

1//! Conversions between numbers and the primitive types: [`From`] every primitive integer,
2//! [`TryFrom`] a number to each of them, and [`Number::to_f64`] and [`Number::to_f32`], correctly
3//! rounded.
4
5use super::cmp::Magnitude;
6use super::{ByteOrder, Number};
7use core::fmt;
8
9/// The error of a conversion of a [`Number`] to a primitive integer type that cannot hold it.
10///
11/// # Examples
12///
13/// ```
14/// use a::math::numbers::{ByteOrder, Number};
15///
16/// let n = Number::new_from_i32(-1, ByteOrder::BigEndian);
17/// assert!(u8::try_from(&n).is_err());
18/// assert_eq!(i8::try_from(&n), Ok(-1));
19/// ```
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct TryFromNumberError(());
22
23impl fmt::Display for TryFromNumberError {
24    /// Writes what was wrong, in the words of the primitive integers' `TryFromIntError`.
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        "out of range integral type conversion attempted".fmt(f)
27    }
28}
29
30impl std::error::Error for TryFromNumberError {}
31
32/// The sign and the magnitude of `n` when the magnitude fits in 16 bytes: the sign is never set
33/// for zero.
34#[inline(always)]
35fn small_parts(n: &Number) -> Option<(bool, u128)> {
36    let m = Magnitude::of(n);
37    (m.bytes.len() <= 16).then(|| (m.negative, m.small()))
38}
39
40/// `From` and `TryFrom` between numbers and the unsigned primitive integer types.
41macro_rules! unsigned {
42    ($($t:ident $new:ident),* $(,)?) => {$(
43        impl From<$t> for Number {
44            #[doc = concat!("Converts a `", stringify!($t), "` to a number, little-endian, as [`Number::", stringify!($new), "`] builds it.")]
45            #[inline]
46            fn from(value: $t) -> Number {
47                Number::$new(value, ByteOrder::LittleEndian)
48            }
49        }
50
51        impl TryFrom<&Number> for $t {
52            type Error = TryFromNumberError;
53
54            #[doc = concat!("Converts a number to a `", stringify!($t), "`, or fails when the number is negative or too large for it.")]
55            #[inline]
56            fn try_from(n: &Number) -> Result<$t, TryFromNumberError> {
57                match small_parts(n) {
58                    Some((false, m)) => <$t>::try_from(m).map_err(|_| TryFromNumberError(())),
59                    _ => Err(TryFromNumberError(())),
60                }
61            }
62        }
63
64        impl TryFrom<Number> for $t {
65            type Error = TryFromNumberError;
66
67            #[doc = concat!("Converts a number to a `", stringify!($t), "`, as from a borrowed number.")]
68            #[inline]
69            fn try_from(n: Number) -> Result<$t, TryFromNumberError> {
70                <$t>::try_from(&n)
71            }
72        }
73    )*};
74}
75
76/// `From` and `TryFrom` between numbers and the signed primitive integer types, with the
77/// unsigned type of the same width.
78macro_rules! signed {
79    ($($t:ident $u:ident $new:ident),* $(,)?) => {$(
80        impl From<$t> for Number {
81            #[doc = concat!("Converts an `", stringify!($t), "` to a number, little-endian, as [`Number::", stringify!($new), "`] builds it.")]
82            #[inline]
83            fn from(value: $t) -> Number {
84                Number::$new(value, ByteOrder::LittleEndian)
85            }
86        }
87
88        impl TryFrom<&Number> for $t {
89            type Error = TryFromNumberError;
90
91            #[doc = concat!("Converts a number to an `", stringify!($t), "`, or fails when it is out of the type's range.")]
92            #[inline]
93            fn try_from(n: &Number) -> Result<$t, TryFromNumberError> {
94                let Some((negative, m)) = small_parts(n) else {
95                    return Err(TryFromNumberError(()));
96                };
97                let m = <$u>::try_from(m).map_err(|_| TryFromNumberError(()))?;
98                if negative {
99                    // The magnitude of `MIN` is one more than `MAX`: it is the most a negative
100                    // number may have, and its two's complement is `MIN` itself.
101                    if m > <$t>::MIN.unsigned_abs() {
102                        return Err(TryFromNumberError(()));
103                    }
104                    Ok((m as $t).wrapping_neg())
105                } else {
106                    <$t>::try_from(m).map_err(|_| TryFromNumberError(()))
107                }
108            }
109        }
110
111        impl TryFrom<Number> for $t {
112            type Error = TryFromNumberError;
113
114            #[doc = concat!("Converts a number to an `", stringify!($t), "`, as from a borrowed number.")]
115            #[inline]
116            fn try_from(n: Number) -> Result<$t, TryFromNumberError> {
117                <$t>::try_from(&n)
118            }
119        }
120    )*};
121}
122
123unsigned!(
124    u8 new_from_u8,
125    u16 new_from_u16,
126    u32 new_from_u32,
127    u64 new_from_u64,
128    u128 new_from_u128,
129    usize new_from_usize,
130);
131signed!(
132    i8 u8 new_from_i8,
133    i16 u16 new_from_i16,
134    i32 u32 new_from_i32,
135    i64 u64 new_from_i64,
136    i128 u128 new_from_i128,
137    isize usize new_from_isize,
138);
139
140/// The number of bits and the top 64 bits of the magnitude `m`, of more than 16 bytes: the bits
141/// from the most significant set one down, shifted up so that it is bit 63.
142#[inline(always)]
143fn top_bits(m: Magnitude) -> (u64, u64) {
144    let v = m.bytes;
145    let n = v.len();
146    debug_assert!(n > 16);
147    // The eight most significant bytes and the ninth.
148    let (high, ninth) = if m.big_endian {
149        (
150            u64::from_be_bytes(v[..8].try_into().unwrap_or_default()),
151            v[8],
152        )
153    } else {
154        (
155            u64::from_le_bytes(v[n - 8..].try_into().unwrap_or_default()),
156            v[n - 9],
157        )
158    };
159    let lz = high.leading_zeros();
160    debug_assert!(lz < 8);
161    let bits = 8 * n as u64 - u64::from(lz);
162    // The ninth byte supplies the `lz` bits that the shift brings in at the bottom.
163    (bits, (high << lz) | (u64::from(ninth) >> (8 - lz)))
164}
165
166/// Whether any bit of the magnitude below its top `64` bits is set: of its `bits` bits, the
167/// `bits - 64` lowest.
168#[cold]
169fn sticky(m: Magnitude, bits: u64) -> bool {
170    let low = bits - 64;
171    let (whole, part) = ((low / 8) as usize, (low % 8) as u32);
172    let v = m.bytes;
173    let n = v.len();
174    let (bytes, partial) = if m.big_endian {
175        (&v[n - whole..], v[n - whole - 1])
176    } else {
177        (&v[..whole], v[whole])
178    };
179    bytes.iter().any(|&b| b != 0) || partial & ((1u8 << part) - 1) != 0
180}
181
182impl Number {
183    /// Returns the number as the nearest `f64`, ties to even, as the conversions of the primitive
184    /// integers with `as` do; infinity, with the number's sign, when it is too large.
185    ///
186    /// A magnitude of up to 16 bytes is converted as a `u128` is. Of a longer one, the top 64 bits
187    /// are converted by the hardware and scaled by a power of two, which is exact: the bits below
188    /// them can only matter when the top ones are exactly halfway between two `f64`s, and are read
189    /// only then.
190    ///
191    /// # Examples
192    ///
193    /// ```
194    /// use a::math::numbers::{ByteOrder, Number};
195    ///
196    /// assert_eq!(Number::new_from_i64(-12345, ByteOrder::BigEndian).to_f64(), -12345.0);
197    /// // 2^53 + 1 is halfway between two f64s: to the even one.
198    /// assert_eq!(Number::new_from_u64((1 << 53) + 1, ByteOrder::BigEndian).to_f64(), 9007199254740992.0);
199    /// let n = Number::from_str_radix(&"9".repeat(400), 10, ByteOrder::LittleEndian).unwrap();
200    /// assert_eq!(n.to_f64(), f64::INFINITY);
201    /// ```
202    pub fn to_f64(&self) -> f64 {
203        let m = Magnitude::of(self);
204        if m.bytes.len() <= 16 {
205            // Rounded once, as the conversions of `u128` round.
206            let magnitude = m.small() as f64;
207            return if m.negative { -magnitude } else { magnitude };
208        }
209        let (bits, top) = top_bits(m);
210        let magnitude = if bits > 1024 {
211            f64::INFINITY
212        } else {
213            // The 53 bits of an `f64` are the top ones, and bits 0 to 10 decide the rounding:
214            // below `0x400` down, above up, and at `0x400` exactly the bits below the top 64 do.
215            let halfway = top & 0x7FF == 0x400;
216            let top = top | u64::from(halfway && sticky(m, bits));
217            // `2^(bits - 64)`, from `2^65` to `2^960`.
218            let scale = f64::from_bits((bits - 64 + 1023) << 52);
219            top as f64 * scale
220        };
221        if m.negative { -magnitude } else { magnitude }
222    }
223
224    /// Returns the number as the nearest `f32`, ties to even, as the conversions of the primitive
225    /// integers with `as` do; infinity, with the number's sign, when it is too large. It is
226    /// rounded once, from the number itself: rounding the [`to_f64`](Self::to_f64) to an `f32`
227    /// would round twice, and could be one off.
228    ///
229    /// # Examples
230    ///
231    /// ```
232    /// use a::math::numbers::{ByteOrder, Number};
233    ///
234    /// assert_eq!(Number::new_from_i64(-12345, ByteOrder::BigEndian).to_f32(), -12345.0);
235    /// assert_eq!(Number::new_from_u128(u128::MAX, ByteOrder::BigEndian).to_f32(), f32::INFINITY);
236    /// ```
237    pub fn to_f32(&self) -> f32 {
238        let m = Magnitude::of(self);
239        // Above 16 bytes a magnitude is at least `2^128`, beyond the largest `f32` and halfway to
240        // the next power of two: infinite.
241        let magnitude = if m.bytes.len() <= 16 {
242            // Rounded once, as the conversions of `u128` round.
243            m.small() as f32
244        } else {
245            f32::INFINITY
246        };
247        if m.negative { -magnitude } else { magnitude }
248    }
249}