Skip to main content

dashu_ratio/
convert.rs

1use core::cmp::Ordering;
2
3use dashu_base::{
4    Approximation::{self, *},
5    BitTest, ConversionError, DivRem, FloatEncoding, PowerOfTwo, Sign, UnsignedAbs,
6};
7use dashu_int::{IBig, UBig};
8
9use crate::{
10    rbig::{RBig, Relaxed},
11    repr::Repr,
12};
13
14impl From<UBig> for Repr {
15    #[inline]
16    fn from(v: UBig) -> Self {
17        Repr {
18            numerator: v.into(),
19            denominator: UBig::ONE,
20        }
21    }
22}
23
24impl From<IBig> for Repr {
25    #[inline]
26    fn from(v: IBig) -> Self {
27        Repr {
28            numerator: v,
29            denominator: UBig::ONE,
30        }
31    }
32}
33
34impl TryFrom<Repr> for UBig {
35    type Error = ConversionError;
36    #[inline]
37    fn try_from(value: Repr) -> Result<Self, Self::Error> {
38        // Integer iff the (reduced) denominator is 1; then the value is the numerator magnitude.
39        if !value.denominator.is_one() {
40            return Err(ConversionError::LossOfPrecision);
41        }
42        let (sign, mag) = value.numerator.into_parts();
43        if sign == Sign::Negative {
44            Err(ConversionError::OutOfBounds)
45        } else {
46            Ok(mag)
47        }
48    }
49}
50
51impl TryFrom<Repr> for IBig {
52    type Error = ConversionError;
53    #[inline]
54    fn try_from(value: Repr) -> Result<Self, Self::Error> {
55        if value.denominator.is_one() {
56            Ok(value.numerator)
57        } else {
58            Err(ConversionError::LossOfPrecision)
59        }
60    }
61}
62
63macro_rules! forward_conversion_to_repr {
64    ($from:ty => $t:ident) => {
65        impl From<$from> for $t {
66            #[inline]
67            fn from(v: $from) -> Self {
68                $t(Repr::from(v))
69            }
70        }
71        impl TryFrom<$t> for $from {
72            type Error = ConversionError;
73            #[inline]
74            fn try_from(value: $t) -> Result<Self, Self::Error> {
75                Self::try_from(value.0)
76            }
77        }
78    };
79}
80forward_conversion_to_repr!(UBig => RBig);
81forward_conversion_to_repr!(IBig => RBig);
82forward_conversion_to_repr!(UBig => Relaxed);
83forward_conversion_to_repr!(IBig => Relaxed);
84
85macro_rules! impl_conversion_for_prim_ints {
86    ($($t:ty)*) => {$(
87        impl From<$t> for Repr {
88            #[inline]
89            fn from(v: $t) -> Repr {
90                Repr {
91                    numerator: v.into(),
92                    denominator: UBig::ONE
93                }
94            }
95        }
96
97        impl TryFrom<Repr> for $t {
98            type Error = ConversionError;
99            #[inline]
100            fn try_from(value: Repr) -> Result<Self, Self::Error> {
101                let int: IBig = value.try_into()?;
102                int.try_into()
103            }
104        }
105
106        forward_conversion_to_repr!($t => RBig);
107        forward_conversion_to_repr!($t => Relaxed);
108    )*};
109}
110impl_conversion_for_prim_ints!(u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize);
111
112macro_rules! impl_conversion_from_float {
113    ($t:ty) => {
114        impl TryFrom<$t> for Repr {
115            type Error = ConversionError;
116
117            fn try_from(value: $t) -> Result<Self, Self::Error> {
118                // shortcut to prevent issues in counting leading zeros
119                if value == 0. {
120                    return Ok(Repr::zero());
121                }
122
123                match value.decode() {
124                    Ok((man, exp)) => {
125                        // here we don't remove the common factor 2, because we need exact
126                        // exponent value in some cases (like approx_f32 and approx_f64)
127                        let repr = if exp >= 0 {
128                            Repr {
129                                numerator: IBig::from(man) << exp as usize,
130                                denominator: UBig::ONE,
131                            }
132                        } else {
133                            let mut denominator = UBig::ZERO;
134                            denominator.set_bit((-exp) as _);
135                            Repr {
136                                numerator: IBig::from(man),
137                                denominator,
138                            }
139                        };
140                        Ok(repr)
141                    }
142                    Err(_) => Err(ConversionError::OutOfBounds),
143                }
144            }
145        }
146
147        impl TryFrom<$t> for RBig {
148            type Error = ConversionError;
149            #[inline]
150            fn try_from(value: $t) -> Result<Self, Self::Error> {
151                Repr::try_from(value).map(|repr| RBig(repr.reduce2()))
152            }
153        }
154        impl TryFrom<$t> for Relaxed {
155            type Error = ConversionError;
156            #[inline]
157            fn try_from(value: $t) -> Result<Self, Self::Error> {
158                Repr::try_from(value).map(|repr| Relaxed(repr.reduce2()))
159            }
160        }
161    };
162}
163impl_conversion_from_float!(f32);
164impl_conversion_from_float!(f64);
165
166macro_rules! impl_conversion_to_float {
167    ($t:ty [$lb:literal, $ub:literal]) => {
168        impl TryFrom<RBig> for $t {
169            type Error = ConversionError;
170
171            /// Convert RBig to primitive floats. It returns [Ok] only if
172            /// the conversion can be done losslessly
173            fn try_from(value: RBig) -> Result<Self, Self::Error> {
174                if value.0.numerator.is_zero() {
175                    Ok(0.)
176                } else if value.0.denominator.is_power_of_two() {
177                    // conversion is exact only if the denominator is a power of two
178                    let num_bits = value.0.numerator.bit_len();
179                    let den_bits = value.0.denominator.trailing_zeros().unwrap();
180                    let top_bit = num_bits as isize - den_bits as isize;
181                    if top_bit > $ub {
182                        // see to_f32::encode for explanation of the bounds
183                        Err(ConversionError::OutOfBounds)
184                    } else if top_bit < $lb {
185                        Err(ConversionError::LossOfPrecision)
186                    } else {
187                        match <$t>::encode(
188                            value.0.numerator.try_into().unwrap(),
189                            -(den_bits as i16),
190                        ) {
191                            Exact(v) => Ok(v),
192                            Inexact(v, _) => {
193                                if v.is_infinite() {
194                                    Err(ConversionError::OutOfBounds)
195                                } else {
196                                    Err(ConversionError::LossOfPrecision)
197                                }
198                            }
199                        }
200                    }
201                } else {
202                    Err(ConversionError::LossOfPrecision)
203                }
204            }
205        }
206
207        impl TryFrom<Relaxed> for $t {
208            type Error = ConversionError;
209
210            #[inline]
211            fn try_from(value: Relaxed) -> Result<Self, Self::Error> {
212                // convert to RBig to eliminate cofactors
213                <$t>::try_from(value.canonicalize())
214            }
215        }
216    };
217}
218impl_conversion_to_float!(f32 [-149, 128]); // see f32::encode for explanation of the bounds
219impl_conversion_to_float!(f64 [-1074, 1024]); // see f32::encode for explanation of the bounds
220
221/// Compute `floor(log2(|numerator/denominator|))`, given the unsigned
222/// numerator magnitude and `exp = numerator.bit_len() - denominator.bit_len()`.
223///
224/// Since `|numerator/denominator| ∈ [2^(exp-1), 2^(exp+1))`, the result is
225/// either `exp` or `exp - 1`; this disambiguates with a single comparison.
226fn log2_floor_abs(numerator: &UBig, denominator: &UBig, exp: isize) -> isize {
227    let ge_power = if exp >= 0 {
228        numerator >= &(denominator << exp as usize)
229    } else {
230        &(numerator << (-exp) as usize) >= denominator
231    };
232    if ge_power {
233        exp
234    } else {
235        exp - 1
236    }
237}
238
239/// Round `|numerator/denominator| * 2^(-shift)` to the nearest integer
240/// (ties to even), where `numerator` is the unsigned magnitude and `sign`
241/// is the sign of the original rational.
242fn rounded_abs_mantissa(
243    numerator: UBig,
244    denominator: &UBig,
245    sign: Sign,
246    shift: isize,
247) -> Approximation<UBig, Sign> {
248    let (num, den) = if shift >= 0 {
249        (numerator, denominator << shift as usize)
250    } else {
251        (numerator << (-shift) as usize, denominator.clone())
252    };
253    let (man, r) = num.div_rem(&den);
254
255    if r.is_zero() {
256        Exact(man)
257    } else {
258        let half = (r << 1).cmp(&den);
259        if half == Ordering::Greater || (half == Ordering::Equal && man.bit(0)) {
260            Inexact(man + UBig::ONE, sign)
261        } else {
262            Inexact(man, -sign)
263        }
264    }
265}
266
267impl Repr {
268    /// Convert the rational number to [f32] without guaranteed correct rounding.
269    fn to_f32_fast(&self) -> f32 {
270        // shortcut
271        if self.numerator.is_zero() {
272            return 0.;
273        }
274
275        // to get enough precision (24 bits), we need to do a 48 by 24 bit division
276        let sign = self.numerator.sign();
277        let num_bits = self.numerator.bit_len();
278        let den_bits = self.denominator.bit_len();
279
280        let num_shift = num_bits as isize - 48;
281        let num48: i64 = if num_shift >= 0 {
282            (&self.numerator) >> num_shift as usize
283        } else {
284            (&self.numerator) << (-num_shift) as usize
285        }
286        .try_into()
287        .unwrap();
288
289        let den_shift = den_bits as isize - 24;
290        let den24: u32 = if den_shift >= 0 {
291            (&self.denominator) >> den_shift as usize
292        } else {
293            (&self.denominator) << (-den_shift) as usize
294        }
295        .try_into()
296        .unwrap();
297
298        // determine the exponent
299        let exponent = num_shift - den_shift;
300        if exponent >= 128 {
301            // max f32 = 2^128 * (1 - 2^-24)
302            sign * f32::INFINITY
303        } else if exponent < -149 - 25 {
304            // min f32 = 2^-149, quotient has at most 25 bits
305            sign * 0f32
306        } else {
307            let (mut man, r) = num48.unsigned_abs().div_rem(den24 as u64);
308
309            // round to nearest, ties to even
310            let half = (r as u32 * 2).cmp(&den24);
311            if half == Ordering::Greater || (half == Ordering::Equal && man & 1 > 0) {
312                man += 1;
313            }
314            f32::encode(sign * man as i32, exponent as i16).value()
315        }
316    }
317
318    fn to_f64_fast(&self) -> f64 {
319        // shortcut
320        if self.numerator.is_zero() {
321            return 0.;
322        }
323
324        // to get enough precision (53 bits), we need to do a 106 by 53 bit division
325        let sign = self.numerator.sign();
326        let num_bits = self.numerator.bit_len();
327        let den_bits = self.denominator.bit_len();
328
329        let num_shift = num_bits as isize - 106;
330        let num106: i128 = if num_shift >= 0 {
331            (&self.numerator) >> num_shift as usize
332        } else {
333            (&self.numerator) << (-num_shift) as usize
334        }
335        .try_into()
336        .unwrap();
337
338        let den_shift = den_bits as isize - 53;
339        let den53: u64 = if den_shift >= 0 {
340            (&self.denominator) >> den_shift as usize
341        } else {
342            (&self.denominator) << (-den_shift) as usize
343        }
344        .try_into()
345        .unwrap();
346
347        // determine the exponent
348        let exponent = num_shift - den_shift;
349        if exponent >= 1024 {
350            // max f64 = 2^1024 × (1 − 2^−53)
351            sign * f64::INFINITY
352        } else if exponent < -1074 - 54 {
353            // min f64 = 2^-1074, quotient has at most 54 bits
354            sign * 0f64
355        } else {
356            let (mut man, r) = num106.unsigned_abs().div_rem(den53 as u128);
357
358            // round to nearest, ties to even
359            let half = (r as u64 * 2).cmp(&den53);
360            if half == Ordering::Greater || (half == Ordering::Equal && man & 1 > 0) {
361                man += 1;
362            }
363            f64::encode(sign * man as i64, exponent as i16).value()
364        }
365    }
366
367    /// Convert the rational number to [f32] with guaranteed correct rounding.
368    fn to_f32(&self) -> Approximation<f32, Sign> {
369        // shortcut
370        if self.numerator.is_zero() {
371            return Exact(0.);
372        }
373
374        let sign = self.numerator.sign();
375        let numerator = (&self.numerator).unsigned_abs();
376        // The bit-length difference bounds the binary exponent:
377        // `top_exp = floor(log2(|value|))` is either `exp` or `exp - 1`.
378        // Fast-path the definite overflow/underflow range from this O(1) bound
379        // so the comparison (and its shift) inside `log2_floor_abs` stays bounded.
380        let exp = numerator.bit_len() as isize - self.denominator.bit_len() as isize;
381        if exp >= 129 {
382            // top_exp >= 128, so |value| >= 2^128 > f32::MAX
383            return Inexact(sign * f32::INFINITY, sign);
384        } else if exp <= -151 {
385            // top_exp <= -151 < -150, so |value| < 2^-150, which rounds to zero
386            return Inexact(sign * 0f32, -sign);
387        }
388
389        let top_exp = log2_floor_abs(&numerator, &self.denominator, exp);
390        if top_exp >= 128 {
391            // max f32 = 2^128 * (1 - 2^-24)
392            Inexact(sign * f32::INFINITY, sign)
393        } else if top_exp < -150 {
394            // values < 2^-150 round to zero; 2^-150 is the half-way tie, rounded to even (zero)
395            Inexact(sign * 0f32, -sign)
396        } else {
397            // scale |value| by 2^(-shift) into [2^23, 2^24) so the rounded mantissa
398            // fits f32's 24-bit significand exactly (no second rounding in encode).
399            // Clamp to the subnormal quantization 2^-149 for tiny magnitudes.
400            let shift = (top_exp - 23).max(-149);
401            rounded_abs_mantissa(numerator, &self.denominator, sign, shift).and_then(|man| {
402                let man: u32 = man.try_into().unwrap();
403                if man == 0 {
404                    // encode(0, _) yields +0; preserve the sign of an underflowed zero
405                    Exact(sign * 0f32)
406                } else {
407                    f32::encode(sign * man as i32, shift as i16)
408                }
409            })
410        }
411    }
412
413    fn to_f64(&self) -> Approximation<f64, Sign> {
414        // shortcut
415        if self.numerator.is_zero() {
416            return Exact(0.);
417        }
418
419        let sign = self.numerator.sign();
420        let numerator = (&self.numerator).unsigned_abs();
421        // See `to_f32` for the rationale on fast-pathing via the bit-length bound.
422        let exp = numerator.bit_len() as isize - self.denominator.bit_len() as isize;
423        if exp >= 1025 {
424            // top_exp >= 1024, so |value| >= 2^1024 > f64::MAX
425            return Inexact(sign * f64::INFINITY, sign);
426        } else if exp <= -1076 {
427            // top_exp <= -1076 < -1075, so |value| < 2^-1075, which rounds to zero
428            return Inexact(sign * 0f64, -sign);
429        }
430
431        let top_exp = log2_floor_abs(&numerator, &self.denominator, exp);
432        if top_exp >= 1024 {
433            // max f64 = 2^1024 × (1 − 2^−53)
434            Inexact(sign * f64::INFINITY, sign)
435        } else if top_exp < -1075 {
436            // values < 2^-1075 round to zero; 2^-1075 is the half-way tie, rounded to even (zero)
437            Inexact(sign * 0f64, -sign)
438        } else {
439            // scale |value| into [2^52, 2^53) for an exact 53-bit mantissa (no second rounding)
440            let shift = (top_exp - 52).max(-1074);
441            rounded_abs_mantissa(numerator, &self.denominator, sign, shift).and_then(|man| {
442                let man: u64 = man.try_into().unwrap();
443                if man == 0 {
444                    Exact(sign * 0f64)
445                } else {
446                    f64::encode(sign * man as i64, shift as i16)
447                }
448            })
449        }
450    }
451}
452
453impl RBig {
454    /// Convert the rational number to a [f32].
455    ///
456    /// The rounding follows the default IEEE 754 behavior (rounds to nearest,
457    /// ties to even).
458    ///
459    /// The rounding will be correct at most of the time, but in rare cases the
460    /// mantissa can be off by one bit. Use [RBig::to_f32] for ensured correct
461    /// rounding.
462    ///
463    /// # Examples
464    ///
465    /// ```
466    /// # use dashu_ratio::RBig;
467    /// assert_eq!(RBig::ONE.to_f32_fast(), 1f32);
468    ///
469    /// let r = RBig::from_parts(22.into(), 7u8.into());
470    /// assert_eq!(r.to_f32_fast(), 22./7.)
471    /// ```
472    #[inline]
473    pub fn to_f32_fast(&self) -> f32 {
474        self.0.to_f32_fast()
475    }
476
477    /// Convert the rational number to a [f64].
478    ///
479    /// The rounding follows the default IEEE 754 behavior (rounds to nearest,
480    /// ties to even).
481    ///
482    /// The rounding will be correct at most of the time, but in rare cases the
483    /// mantissa can be off by one bit. Use [RBig::to_f64] for ensured correct
484    /// rounding.
485    ///
486    /// # Examples
487    ///
488    /// ```
489    /// # use dashu_ratio::RBig;
490    /// assert_eq!(RBig::ONE.to_f64_fast(), 1f64);
491    ///
492    /// let r = RBig::from_parts(22.into(), 7u8.into());
493    /// assert_eq!(r.to_f64_fast(), 22./7.)
494    /// ```
495    #[inline]
496    pub fn to_f64_fast(&self) -> f64 {
497        self.0.to_f64_fast()
498    }
499
500    /// Convert the rational number to a [f32] with guaranteed correct rounding.
501    ///
502    /// The rounding follows the default IEEE 754 behavior (rounds to nearest,
503    /// ties to even).
504    ///
505    /// Because of the guaranteed rounding, it might take a long time to convert
506    /// when the numerator and denominator are large. In this case [RBig::to_f32_fast]
507    /// can be used if the correct rounding is not required.
508    ///
509    /// # Examples
510    ///
511    /// ```
512    /// # use dashu_base::{Approximation::*, Sign::*};
513    /// # use dashu_ratio::RBig;
514    /// assert_eq!(RBig::ONE.to_f32(), Exact(1f32));
515    ///
516    /// let r = RBig::from_parts(22.into(), 7u8.into());
517    /// // f32 representation of 22/7 is smaller than the actual 22/7
518    /// assert_eq!(r.to_f32(), Inexact(22./7., Negative));
519    /// ```
520    #[inline]
521    pub fn to_f32(&self) -> Approximation<f32, Sign> {
522        self.0.to_f32()
523    }
524
525    /// Convert the rational number to a [f64] with guaranteed correct rounding.
526    ///
527    /// The rounding follows the default IEEE 754 behavior (rounds to nearest,
528    /// ties to even).
529    ///
530    /// Because of the guaranteed rounding, it might take a long time to convert
531    /// when the numerator and denominator are large. In this case [RBig::to_f64_fast]
532    /// can be used if the correct rounding is not required.
533    ///
534    /// # Examples
535    ///
536    /// ```
537    /// # use dashu_base::{Approximation::*, Sign::*};
538    /// # use dashu_ratio::RBig;
539    /// assert_eq!(RBig::ONE.to_f64(), Exact(1f64));
540    ///
541    /// let r = RBig::from_parts(22.into(), 7u8.into());
542    /// // f64 representation of 22/7 is smaller than the actual 22/7
543    /// assert_eq!(r.to_f64(), Inexact(22./7., Negative));
544    /// ```
545    #[inline]
546    pub fn to_f64(&self) -> Approximation<f64, Sign> {
547        self.0.to_f64()
548    }
549
550    /// Convert the rational number to an [IBig].
551    ///
552    /// The conversion rounds toward zero. It's equivalent to [RBig::trunc],
553    /// but it returns the fractional part if the rational number is not an integer.
554    ///
555    /// # Examples
556    ///
557    /// ```
558    /// # use dashu_base::Approximation::*;
559    /// # use dashu_int::{IBig, UBig};
560    /// # use dashu_ratio::RBig;
561    /// let a = RBig::from_parts(22.into(), UBig::ONE);
562    /// assert_eq!(a.to_int(), Exact(IBig::from(22)));
563    ///
564    /// let b = RBig::from_parts(22.into(), 7u8.into());
565    /// assert_eq!(b.to_int(), Inexact(
566    ///     IBig::from(3), RBig::from_parts(1.into(), 7u8.into())
567    /// ));
568    /// ```
569    #[inline]
570    pub fn to_int(&self) -> Approximation<IBig, Self> {
571        let (trunc, fract) = self.clone().split_at_point();
572        if fract.is_zero() {
573            Approximation::Exact(trunc)
574        } else {
575            Approximation::Inexact(trunc, fract)
576        }
577    }
578}
579
580impl Relaxed {
581    /// Convert the rational number to a [f32].
582    ///
583    /// See [RBig::to_f32_fast] for details.
584    #[inline]
585    pub fn to_f32_fast(&self) -> f32 {
586        self.0.to_f32_fast()
587    }
588    /// Convert the rational number to a [f64].
589    ///
590    /// See [RBig::to_f64_fast] for details.
591    #[inline]
592    pub fn to_f64_fast(&self) -> f64 {
593        self.0.to_f64_fast()
594    }
595
596    /// Convert the rational number to a [f32] with guaranteed correct rounding.
597    ///
598    /// See [RBig::to_f32] for details.
599    #[inline]
600    pub fn to_f32(&self) -> Approximation<f32, Sign> {
601        self.0.to_f32()
602    }
603    /// Convert the rational number to a [f64] with guaranteed correct rounding.
604    ///
605    /// See [RBig::to_f64] for details.
606    #[inline]
607    pub fn to_f64(&self) -> Approximation<f64, Sign> {
608        self.0.to_f64()
609    }
610    /// Convert the rational number to am [IBig].
611    ///
612    /// See [RBig::to_int] for details.
613    #[inline]
614    pub fn to_int(&self) -> Approximation<IBig, Self> {
615        let (trunc, fract) = self.clone().split_at_point();
616        if fract.is_zero() {
617            Approximation::Exact(trunc)
618        } else {
619            Approximation::Inexact(trunc, fract)
620        }
621    }
622}
623
624#[cfg(test)]
625mod tests {
626    use super::*;
627    use dashu_base::Sign::{Negative, Positive};
628
629    #[test]
630    fn test_ubig_try_from_repr() {
631        // Integer values (reduced denominator == 1) convert to their magnitude.
632        assert_eq!(UBig::try_from(RBig::from_parts(5.into(), UBig::ONE)), Ok(UBig::from(5u8)));
633        assert_eq!(UBig::try_from(RBig::ZERO), Ok(UBig::ZERO));
634        // A proper fraction is not an integer — must not silently succeed as the numerator.
635        assert_eq!(
636            UBig::try_from(RBig::from_parts(1.into(), 2u8.into())),
637            Err(ConversionError::LossOfPrecision)
638        );
639        // A negative integer is out of the unsigned range.
640        assert_eq!(
641            UBig::try_from(RBig::from_parts((-3).into(), UBig::ONE)),
642            Err(ConversionError::OutOfBounds)
643        );
644    }
645
646    #[test]
647    fn test_rbig_to_f64_without_double_rounding() {
648        let input =
649            RBig::from_parts((-10534148920556696739i128).into(), 73786976294838206464u128.into());
650
651        assert_eq!(input.to_f64(), Inexact(f64::from_bits(0xbfc2_461a_1430_9b17), Negative));
652        assert_eq!((-input).to_f64(), Inexact(f64::from_bits(0x3fc2_461a_1430_9b17), Positive));
653    }
654
655    #[test]
656    fn test_rbig_to_float_midpoints_tie_to_even() {
657        assert_eq!(
658            RBig::from_parts(((1u64 << 24) + 1).into(), (1u64 << 24).into()).to_f32(),
659            Inexact(1.0, Negative)
660        );
661        assert_eq!(
662            RBig::from_parts(((1u64 << 24) + 3).into(), (1u64 << 24).into()).to_f32(),
663            Inexact(f32::from_bits(0x3f80_0002), Positive)
664        );
665
666        assert_eq!(
667            RBig::from_parts(((1u64 << 53) + 1).into(), (1u64 << 53).into()).to_f64(),
668            Inexact(1.0, Negative)
669        );
670        assert_eq!(
671            RBig::from_parts(((1u64 << 53) + 3).into(), (1u64 << 53).into()).to_f64(),
672            Inexact(f64::from_bits(0x3ff0_0000_0000_0002), Positive)
673        );
674    }
675
676    #[test]
677    fn test_rbig_to_float_around_midpoints() {
678        assert_eq!(
679            RBig::from_parts(((1u64 << 25) + 1).into(), (1u64 << 25).into()).to_f32(),
680            Inexact(1.0, Negative)
681        );
682        assert_eq!(
683            RBig::from_parts(((1u64 << 25) + 3).into(), (1u64 << 25).into()).to_f32(),
684            Inexact(f32::from_bits(0x3f80_0001), Positive)
685        );
686
687        assert_eq!(
688            RBig::from_parts(((1u128 << 54) + 1).into(), (1u128 << 54).into()).to_f64(),
689            Inexact(1.0, Negative)
690        );
691        assert_eq!(
692            RBig::from_parts(((1u128 << 54) + 3).into(), (1u128 << 54).into()).to_f64(),
693            Inexact(f64::from_bits(0x3ff0_0000_0000_0001), Positive)
694        );
695    }
696
697    #[test]
698    fn test_rbig_to_float_subnormal_rounding_boundaries() {
699        assert_eq!(RBig::from_parts(1.into(), UBig::ONE << 150).to_f32(), Inexact(0.0, Negative));
700        assert_eq!(
701            RBig::from_parts((-1).into(), UBig::ONE << 150).to_f32(),
702            Inexact(-0.0, Positive)
703        );
704        assert_eq!(
705            RBig::from_parts(3.into(), UBig::ONE << 151).to_f32(),
706            Inexact(f32::from_bits(1), Positive)
707        );
708        assert_eq!(
709            RBig::from_parts(3.into(), UBig::ONE << 150).to_f32(),
710            Inexact(f32::from_bits(2), Positive)
711        );
712
713        assert_eq!(RBig::from_parts(1.into(), UBig::ONE << 1075).to_f64(), Inexact(0.0, Negative));
714        assert_eq!(
715            RBig::from_parts((-1).into(), UBig::ONE << 1075).to_f64(),
716            Inexact(-0.0, Positive)
717        );
718        assert_eq!(
719            RBig::from_parts(3.into(), UBig::ONE << 1076).to_f64(),
720            Inexact(f64::from_bits(1), Positive)
721        );
722        assert_eq!(
723            RBig::from_parts(3.into(), UBig::ONE << 1075).to_f64(),
724            Inexact(f64::from_bits(2), Positive)
725        );
726    }
727}