Skip to main content

dashu_float/
convert.rs

1use core::{
2    convert::{TryFrom, TryInto},
3    num::FpCategory,
4};
5
6use dashu_base::{
7    AbsOrd, Approximation::*, BitTest, ConversionError, DivExact, DivRemEuclid, EstimatedLog2,
8    FloatEncoding, Sign, Signed,
9};
10use dashu_int::{IBig, UBig, Word};
11
12use crate::{
13    error::{assert_finite, panic_unlimited_precision, FpError, FpResult},
14    fbig::FBig,
15    math::cache::{reborrow_cache, ConstCache},
16    repr::{Context, Repr},
17    round::{
18        mode::{HalfAway, HalfEven, Zero},
19        Round, Rounded, Rounding,
20        Rounding::*,
21    },
22    utils::{factor_base, ilog_exact, shl_digits, shl_digits_in_place, shr_digits},
23};
24
25impl<R: Round> Context<R> {
26    /// Convert an [IBig] instance to a [FBig] instance with precision
27    /// and rounding given by the context.
28    ///
29    /// # Examples
30    ///
31    /// ```
32    /// # use core::str::FromStr;
33    /// # use dashu_base::ParseError;
34    /// # use dashu_float::DBig;
35    /// use dashu_base::Approximation::*;
36    /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
37    ///
38    /// let context = Context::<HalfAway>::new(2);
39    /// assert_eq!(context.convert_int::<10>((-12).into()), Exact(DBig::from_str("-12")?));
40    /// assert_eq!(
41    ///     context.convert_int::<10>(5678.into()),
42    ///     Inexact(DBig::from_str("5.7e3")?, AddOne)
43    /// );
44    /// # Ok::<(), ParseError>(())
45    /// ```
46    pub fn convert_int<const B: Word>(&self, n: IBig) -> Rounded<FBig<R, B>> {
47        let repr = Repr::<B>::new(n, 0);
48        self.repr_round(repr).map(|v| FBig::new(v, *self))
49    }
50}
51
52macro_rules! impl_from_float_for_fbig {
53    ($t:ty) => {
54        impl TryFrom<$t> for Repr<2> {
55            type Error = ConversionError;
56
57            fn try_from(f: $t) -> Result<Self, Self::Error> {
58                match f.decode() {
59                    Ok((man, exp)) => Ok(if man == 0 && f.is_sign_negative() {
60                        Self::neg_zero()
61                    } else {
62                        Repr::new(man.into(), exp as _)
63                    }),
64                    Err(FpCategory::Infinite) => match f.sign() {
65                        Sign::Positive => Ok(Self::infinity()),
66                        Sign::Negative => Ok(Self::neg_infinity()),
67                    },
68                    _ => Err(ConversionError::OutOfBounds), // NaN
69                }
70            }
71        }
72
73        impl<R: Round> TryFrom<$t> for FBig<R, 2> {
74            type Error = ConversionError;
75
76            fn try_from(f: $t) -> Result<Self, Self::Error> {
77                match f.decode() {
78                    Ok((man, exp)) => {
79                        // preserve the sign of a signed zero (-0.0 -> Repr::neg_zero())
80                        let repr = if man == 0 && f.is_sign_negative() {
81                            Repr::neg_zero()
82                        } else {
83                            Repr::new(man.into(), exp as _)
84                        };
85
86                        // The precision is inferenced from the mantissa, because the mantissa of
87                        // normal float is always normalized. This will produce correct precision
88                        // for subnormal floats
89                        let bits = man.unsigned_abs().bit_len();
90                        let context = Context::new(bits);
91                        Ok(Self::new(repr, context))
92                    }
93                    Err(FpCategory::Infinite) => match f.sign() {
94                        Sign::Positive => Ok(Self::INFINITY),
95                        Sign::Negative => Ok(Self::NEG_INFINITY),
96                    },
97                    _ => Err(ConversionError::OutOfBounds), // NaN
98                }
99            }
100        }
101    };
102}
103
104impl_from_float_for_fbig!(f32);
105impl_from_float_for_fbig!(f64);
106
107impl<R: Round, const B: Word> FBig<R, B> {
108    /// Convert the float number to base 10 (with decimal exponents) rounding to even
109    /// and tying away from zero.
110    ///
111    /// It's equivalent to `self.with_rounding::<HalfAway>().with_base::<10>()`.
112    /// The output is directly of type [DBig][crate::DBig].
113    ///
114    /// See [with_base()][Self::with_base] for the precision behavior.
115    ///
116    /// # Examples
117    ///
118    /// ```
119    /// # use core::str::FromStr;
120    /// # use dashu_base::ParseError;
121    /// # use dashu_float::{FBig, DBig};
122    /// use dashu_base::Approximation::*;
123    /// use dashu_float::round::Rounding::*;
124    ///
125    /// type Real = FBig;
126    ///
127    /// assert_eq!(
128    ///     Real::from_str("0x1234")?.to_decimal(),
129    ///     Exact(DBig::from_str("4660")?)
130    /// );
131    /// assert_eq!(
132    ///     Real::from_str("0x12.34")?.to_decimal(),
133    ///     Inexact(DBig::from_str("18.20")?, NoOp)
134    /// );
135    /// assert_eq!(
136    ///     Real::from_str("0x1.234p-4")?.to_decimal(),
137    ///     Inexact(DBig::from_str("0.07111")?, AddOne)
138    /// );
139    /// # Ok::<(), ParseError>(())
140    /// ```
141    ///
142    /// # Panics
143    ///
144    /// Panics if the associated context has unlimited precision and the conversion
145    /// cannot be performed losslessly.
146    #[inline]
147    pub fn to_decimal(&self) -> Rounded<FBig<HalfAway, 10>> {
148        self.clone().with_rounding().with_base::<10>()
149    }
150
151    /// Convert the float number to base 2 (with binary exponents) rounding towards zero.
152    ///
153    /// It's equivalent to `self.with_rounding::<Zero>().with_base::<2>()`.
154    ///
155    /// See [with_base()][Self::with_base] for the precision and rounding behavior.
156    ///
157    /// # Examples
158    ///
159    /// ```
160    /// # use core::str::FromStr;
161    /// # use dashu_base::ParseError;
162    /// # use dashu_float::{FBig, DBig};
163    /// use dashu_base::Approximation::*;
164    /// use dashu_float::round::{mode::HalfAway, Rounding::*};
165    ///
166    /// type Real = FBig;
167    ///
168    /// assert_eq!(
169    ///     DBig::from_str("1234")?.to_binary(),
170    ///     Exact(Real::from_str("0x4d2")?)
171    /// );
172    /// assert_eq!(
173    ///     DBig::from_str("12.34")?.to_binary(),
174    ///     Inexact(Real::from_str("0xc.57")?, NoOp)
175    /// );
176    /// assert_eq!(
177    ///     DBig::from_str("1.234e-1")?.to_binary(),
178    ///     Inexact(Real::from_str("0x1.f97p-4")?, NoOp)
179    /// );
180    /// # Ok::<(), ParseError>(())
181    /// ```
182    ///
183    /// # Panics
184    ///
185    /// Panics if the associated context has unlimited precision and the conversion
186    /// cannot be performed losslessly.
187    #[inline]
188    pub fn to_binary(&self) -> Rounded<FBig<Zero, 2>> {
189        self.clone().with_rounding().with_base::<2>()
190    }
191
192    /// Explicitly change the precision of the float number.
193    ///
194    /// If the given precision is less than the current value in the context,
195    /// it will be rounded with the rounding mode specified by the generic parameter.
196    ///
197    /// # Examples
198    ///
199    /// ```rust
200    /// # use core::str::FromStr;
201    /// # use dashu_base::ParseError;
202    /// # use dashu_float::{FBig, DBig};
203    /// use dashu_base::Approximation::*;
204    /// use dashu_float::round::{mode::HalfAway, Rounding::*};
205    ///
206    /// let a = DBig::from_str("2.345")?;
207    /// assert_eq!(a.precision(), 4);
208    /// assert_eq!(
209    ///     a.clone().with_precision(3),
210    ///     Inexact(DBig::from_str("2.35")?, AddOne)
211    /// );
212    /// assert_eq!(
213    ///     a.clone().with_precision(5),
214    ///     Exact(DBig::from_str("2.345")?)
215    /// );
216    /// # Ok::<(), ParseError>(())
217    /// ```
218    #[inline]
219    pub fn with_precision(self, precision: usize) -> Rounded<Self> {
220        let new_context = Context::new(precision);
221
222        // round if the source is unlimited or the precision shrinks; infinities stay as-is
223        let repr = if self.repr.is_infinite() {
224            Exact(self.repr)
225        } else if !self.context.is_limited() || self.context.precision > precision {
226            new_context.repr_round(self.repr)
227        } else {
228            Exact(self.repr)
229        };
230
231        repr.map(|v| Self::new(v, new_context))
232    }
233
234    /// Explicitly change the rounding mode of the number.
235    ///
236    /// This operation doesn't modify the underlying representation, it only changes
237    /// the rounding mode in the context.
238    ///
239    /// # Examples
240    ///
241    /// ```rust
242    /// # use core::str::FromStr;
243    /// # use dashu_base::ParseError;
244    /// # use dashu_float::{FBig, DBig};
245    /// use dashu_base::Approximation::*;
246    /// use dashu_float::round::{mode::{HalfAway, Zero}, Rounding::*};
247    ///
248    /// type DBigHalfAway = DBig;
249    /// type DBigZero = FBig::<Zero, 10>;
250    ///
251    /// let a = DBigHalfAway::from_str("2.345")?;
252    /// let b = DBigZero::from_str("2.345")?;
253    /// assert_eq!(a.with_rounding::<Zero>(), b);
254    /// # Ok::<(), ParseError>(())
255    /// ```
256    #[inline]
257    pub fn with_rounding<NewR: Round>(self) -> FBig<NewR, B> {
258        FBig {
259            repr: self.repr,
260            context: Context::new(self.context.precision),
261        }
262    }
263
264    /// Explicitly change the base of the float number.
265    ///
266    /// This function internally calls [with_base_and_precision][Self::with_base_and_precision].
267    /// The precision of the result number will be calculated in such a way that the new
268    /// limit of the significand is less than or equal to before. That is, the new precision
269    /// will be the max integer such that
270    ///
271    /// `NewB ^ new_precision <= B ^ old_precision`
272    ///
273    /// If any rounding happens during the conversion, it follows the rounding mode specified
274    /// by the generic parameter.
275    ///
276    /// # Examples
277    ///
278    /// ```rust
279    /// # use core::str::FromStr;
280    /// # use dashu_base::ParseError;
281    /// # use dashu_float::{FBig, DBig};
282    /// use dashu_base::Approximation::*;
283    /// use dashu_float::round::{mode::Zero, Rounding::*};
284    ///
285    /// type FBin = FBig;
286    /// type FDec = FBig<Zero, 10>;
287    /// type FHex = FBig<Zero, 16>;
288    ///
289    /// let a = FBin::from_str("0x1.234")?; // 0x1234 * 2^-12
290    /// assert_eq!(
291    ///     a.clone().with_base::<10>(),
292    ///     // 1.1376953125 rounded towards zero
293    ///     Inexact(FDec::from_str("1.137")?, NoOp)
294    /// );
295    /// assert_eq!(
296    ///     a.clone().with_base::<16>(),
297    ///     // conversion is exact when the new base is a power of the old base
298    ///     Exact(FHex::from_str("1.234")?)
299    /// );
300    /// # Ok::<(), ParseError>(())
301    /// ```
302    ///
303    /// # Panics
304    ///
305    /// Panics if the associated context has unlimited precision and the conversion
306    /// cannot be performed losslessly.
307    #[inline]
308    #[allow(non_upper_case_globals)]
309    pub fn with_base<const NewB: Word>(self) -> Rounded<FBig<R, NewB>> {
310        // if self.context.precision is zero, then precision is also zero
311        let precision =
312            Repr::<B>::BASE.pow(self.context.precision).log2_bounds().0 / NewB.log2_bounds().1;
313        self.with_base_and_precision(precision as usize)
314    }
315
316    /// Explicitly change the base of the float number with given precision (under the new base).
317    ///
318    /// Infinities are mapped to infinities inexactly, the error will be [Rounding::NoOp].
319    ///
320    /// Conversion for float numbers with unlimited precision is only allowed in following cases:
321    /// - The number is infinite
322    /// - The new base NewB is a power of B
323    /// - B is a power of the new base NewB
324    ///
325    /// # Examples
326    ///
327    /// ```rust
328    /// # use core::str::FromStr;
329    /// # use dashu_base::ParseError;
330    /// # use dashu_float::{FBig, DBig};
331    /// use dashu_base::Approximation::*;
332    /// use dashu_float::round::{mode::Zero, Rounding::*};
333    ///
334    /// type FBin = FBig;
335    /// type FDec = FBig<Zero, 10>;
336    /// type FHex = FBig<Zero, 16>;
337    ///
338    /// let a = FBin::from_str("0x1.234")?; // 0x1234 * 2^-12
339    /// assert_eq!(
340    ///     a.clone().with_base_and_precision::<10>(8),
341    ///     // 1.1376953125 rounded towards zero
342    ///     Inexact(FDec::from_str("1.1376953")?, NoOp)
343    /// );
344    /// assert_eq!(
345    ///     a.clone().with_base_and_precision::<16>(8),
346    ///     // conversion can be exact when the new base is a power of the old base
347    ///     Exact(FHex::from_str("1.234")?)
348    /// );
349    /// assert_eq!(
350    ///     a.clone().with_base_and_precision::<16>(2),
351    ///     // but the conversion is still inexact if the target precision is smaller
352    ///     Inexact(FHex::from_str("1.2")?, NoOp)
353    /// );
354    /// # Ok::<(), ParseError>(())
355    /// ```
356    ///
357    /// # Panics
358    ///
359    /// Panics if the associated context has unlimited precision and the conversion
360    /// cannot be performed losslessly.
361    #[allow(non_upper_case_globals)]
362    #[inline]
363    pub fn with_base_and_precision<const NewB: Word>(
364        self,
365        precision: usize,
366    ) -> Rounded<FBig<R, NewB>> {
367        let context = Context::<R>::new(precision);
368        context
369            .convert_base(self.repr, None)
370            .map(|repr| FBig::new(repr, context))
371    }
372
373    /// Convert the float number to integer with the given rounding mode.
374    ///
375    /// # Warning
376    ///
377    /// If the float number has a very large exponent, it will be evaluated and result
378    /// in allocating an huge integer and it might eat up all your memory.
379    ///
380    /// To get a rough idea of how big the number is, it's recommended to use [EstimatedLog2].
381    ///
382    /// # Examples
383    ///
384    /// ```
385    /// # use core::str::FromStr;
386    /// # use dashu_base::ParseError;
387    /// # use dashu_float::{FBig, DBig};
388    /// use dashu_base::Approximation::*;
389    /// use dashu_float::round::Rounding::*;
390    ///
391    /// assert_eq!(
392    ///     DBig::from_str("1234")?.to_int(),
393    ///     Exact(1234.into())
394    /// );
395    /// assert_eq!(
396    ///     DBig::from_str("1.234e6")?.to_int(),
397    ///     Exact(1234000.into())
398    /// );
399    /// assert_eq!(
400    ///     DBig::from_str("1.234")?.to_int(),
401    ///     Inexact(1.into(), NoOp)
402    /// );
403    /// # Ok::<(), ParseError>(())
404    /// ```
405    ///
406    /// # Panics
407    ///
408    /// Panics if the number is infinte
409    pub fn to_int(&self) -> Rounded<IBig> {
410        assert_finite(&self.repr);
411
412        // shortcut when the number is already an integer
413        if self.repr.exponent >= 0 {
414            return Exact(shl_digits::<B>(&self.repr.significand, self.repr.exponent as usize));
415        }
416
417        let (hi, lo, precision) = self.split_at_point_internal();
418        let adjust = R::round_fract::<B>(&hi, lo, precision);
419        Inexact(hi + adjust, adjust)
420    }
421
422    /// Convert the float number to [f32] with the rounding mode associated with the type.
423    ///
424    /// Note that the conversion is inexact even if the number is infinite.
425    ///
426    /// # Examples
427    ///
428    /// ```
429    /// # use core::str::FromStr;
430    /// # use dashu_base::ParseError;
431    /// # use dashu_float::DBig;
432    /// assert_eq!(DBig::from_str("1.234")?.to_f32().value(), 1.234);
433    /// assert_eq!(DBig::INFINITY.to_f32().value(), f32::INFINITY);
434    /// # Ok::<(), ParseError>(())
435    /// ```
436    #[inline]
437    pub fn to_f32(&self) -> Rounded<f32> {
438        match Context::<R>::convert_to_f32(self.repr.clone()) {
439            Ok(rounded) => rounded,
440            Err(err) => f32_directed_endpoint::<R>(err),
441        }
442    }
443
444    /// Convert the float number to [f64] with the rounding mode associated with the type.
445    ///
446    /// Note that the conversion is inexact even if the number is infinite.
447    ///
448    /// # Examples
449    ///
450    /// ```
451    /// # use core::str::FromStr;
452    /// # use dashu_base::ParseError;
453    /// # use dashu_float::DBig;
454    /// assert_eq!(DBig::from_str("1.234")?.to_f64().value(), 1.234);
455    /// assert_eq!(DBig::INFINITY.to_f64().value(), f64::INFINITY);
456    /// # Ok::<(), ParseError>(())
457    /// ```
458    #[inline]
459    pub fn to_f64(&self) -> Rounded<f64> {
460        match Context::<R>::convert_to_f64(self.repr.clone()) {
461            Ok(rounded) => rounded,
462            Err(err) => f64_directed_endpoint::<R>(err),
463        }
464    }
465}
466
467/// `isize` exponent arithmetic overflowed during base conversion: the value's magnitude
468/// falls outside the representable exponent range, so the result is ±infinity (`large`) or
469/// ±0 (`!large`). Mirrors the convention `convert_base` already uses in its division path,
470/// keeping the conversion overflow-safe (no panic) at the value level.
471#[allow(non_upper_case_globals)]
472fn converted_overflow_repr<const NewB: Word>(large: bool, sign: Sign) -> Rounded<Repr<NewB>> {
473    Inexact(
474        if large {
475            Repr::<NewB>::infinity_with_sign(sign)
476        } else {
477            Repr::<NewB>::zero_with_sign(sign)
478        },
479        Rounding::NoOp,
480    )
481}
482
483/// Number of significant bits a binary float format keeps for a value whose most-significant bit
484/// sits at position `msb`: `max_bits` across the normal range, but fewer for subnormals, whose
485/// spacing is fixed at `2^subnormal_exp` (e.g. `2^-1074` for f64, `2^-149` for f32). Rounding the
486/// source straight to this width lets the bit-encoding step avoid a second rounding, which would
487/// otherwise double-round subnormals.
488fn significand_bits(v: &Repr<2>, max_bits: usize, subnormal_exp: isize) -> usize {
489    if v.significand.is_zero() {
490        return max_bits;
491    }
492    let msb = v.exponent + v.digits() as isize - 1;
493    (msb - subnormal_exp + 1).clamp(1, max_bits as isize) as usize
494}
495
496/// Convert `repr` to base 2 and reduce to a `width`-bit round-to-odd value: the top `width` bits
497/// with the lowest kept bit forced to 1 whenever the conversion was inexact. Rounding this to any
498/// width up to `width - 2` reproduces the correctly-rounded value for every rounding mode, so the
499/// two-step "convert, then round to the final width" cannot double-round.
500///
501/// The conversion is first evaluated at `width + GUARD` bits (work precision `2·(width + GUARD)`)
502/// and only then round-to-odd'd down to `width`. The extra guard is required for wide source
503/// significands (e.g. a decimal `FBig` with hundreds of bits): the base-conversion logarithm is
504/// *near-correct* — its ln/exp series carry a few-ulp error at the work precision — so converting
505/// straight at `width` can land a value whose true result sits within ~`2^{-2·width}` of a
506/// `width`-bit midpoint on the wrong side of that midpoint, and the subsequent round picks the
507/// wrong neighbor (a decimal→f32 subnormal off by 1 ULP). The guard pushes that residual error
508/// well below one `width`-bit ulp.
509#[allow(non_upper_case_globals)]
510fn convert_base_odd<const B: Word>(repr: Repr<B>, width: usize) -> Repr<2> {
511    const GUARD: usize = 24;
512    match Context::<Zero>::new(width + GUARD).convert_base::<B, 2>(repr, None) {
513        Exact(v) => v,
514        Inexact(v, _) if v.significand.is_zero() => v,
515        Inexact(v, _) => {
516            let digits = v.digits();
517            let (sign, mut mag) = v.significand.into_parts();
518            // collapse onto exactly `width` significant bits (drop or pad), then force the lowest
519            // kept bit to 1 to mark the inexact conversion (round-to-odd).
520            let exp = if digits >= width {
521                let shift = digits - width;
522                if shift > 0 {
523                    mag >>= shift;
524                }
525                v.exponent + shift as isize
526            } else {
527                let shift = width - digits;
528                mag <<= shift;
529                v.exponent - shift as isize
530            };
531            mag.set_bit(0);
532            Repr::new(IBig::from_parts(sign, mag), exp)
533        }
534    }
535}
536
537impl<R: Round> Context<R> {
538    // Convert `repr` (base B) to the nearest f64 under this context's rounding mode. A generous
539    // round-to-odd base conversion is rounded once to the target's precision at its own magnitude
540    // (fewer than 53 bits for subnormals), so `into_f64_internal` re-rounds nothing — which would
541    // otherwise double-round subnormals. Handles a source significand of any size.
542    //
543    // Returns `Err(FpError::Overflow/Underflow)` when the value is outside the finite f64 range;
544    // the caller decides whether to saturate that to the directed endpoint (`to_f64`) or report it
545    // as a conversion error (`TryFrom`). This makes `into_f64_internal` the single source of truth
546    // for "is this value in range", shared by both APIs.
547    fn convert_to_f64<const B: Word>(repr: Repr<B>) -> FpResult<f64> {
548        if repr.is_infinite() {
549            return Ok(Inexact(repr.sign() * f64::INFINITY, Rounding::NoOp));
550        }
551        // Underflow short-circuit on the *source* value (before base conversion). For a value far
552        // below half the smallest subnormal (`|x| < 2^-1075`), the result is `±0` under nearest and
553        // the smallest subnormal under outward modes — independent of base conversion. Checking the
554        // source matters because converting a catastrophically tiny value (e.g. a wide-significand
555        // decimal at a hugely negative exponent) drives the conversion's internal `exp` to underflow,
556        // yielding an `odd` with a wildly wrong (too large) magnitude that `encode` then fails to
557        // flag. `log2_bounds` on the source is exact (derived from the significand bit length), so
558        // it sees the true magnitude; `ub < -1075` certifies `|x| < 2^-1075 = ½·MIN_SUBNORMAL`.
559        // (Zero is excluded — it is exactly `0`, not an underflow.)
560        if !repr.significand().is_zero() && repr.log2_bounds().1 < -1075.0 {
561            return Err(FpError::Underflow(repr.sign()));
562        }
563        let odd = convert_base_odd::<B>(repr, 60);
564        // Unified range check (shared by `to_f64` and `TryFrom`): a value beyond f64::MAX is out
565        // of range regardless of rounding mode (the mode only picks the saturation endpoint).
566        // Checked on the base-2 `odd`, BEFORE the significand rounding that could collapse a
567        // beyond-MAX value onto MAX (which `encode` would then miss, breaking mode-independence).
568        // `log2_bounds` fast-rejects the common case; the exact `abs_cmp` runs only near MAX.
569        let (lb, ub) = odd.log2_bounds();
570        if lb > 1024.0
571            || (ub >= 1024.0
572                && odd
573                    .abs_cmp(&(UBig::from(0x1FFFFFFFFFFFFFu64) << 971))
574                    .is_gt())
575        {
576            return Err(FpError::Overflow(odd.sign()));
577        }
578        let bits = significand_bits(&odd, 53, -1074);
579        // The base conversion's rounding flag must propagate: `1e20 → f64` is inexact at the
580        // round-to-odd step even though the already-rounded significand then `encode`s exactly.
581        // This mirrors `Approximation::and_then` — an inexact input lifts an exact `encode` to
582        // inexact with the input's flag, while an inexact `encode` keeps its own flag.
583        let rounded = Context::<R>::new(bits).repr_round(odd);
584        match rounded {
585            Exact(v) => v.into_f64_internal(),
586            Inexact(v, e) => match v.into_f64_internal()? {
587                Exact(f) => Ok(Inexact(f, e)),
588                Inexact(f, e2) => Ok(Inexact(f, e2)),
589            },
590        }
591    }
592
593    // [convert_to_f64] for f32.
594    fn convert_to_f32<const B: Word>(repr: Repr<B>) -> FpResult<f32> {
595        if repr.is_infinite() {
596            return Ok(Inexact(repr.sign() * f32::INFINITY, Rounding::NoOp));
597        }
598        // See `convert_to_f64`: underflow short-circuit on the source value. f32's smallest
599        // subnormal is `2^-149`, so `|x| < 2^-150 = ½·MIN_SUBNORMAL` rounds to `±0` (nearest) or
600        // the smallest subnormal (outward). The source `log2_bounds` avoids the base-conversion
601        // magnitude corruption that would otherwise hide a catastrophic underflow from `encode`.
602        // (Zero is excluded — it is exactly `0`, not an underflow.)
603        if !repr.significand().is_zero() && repr.log2_bounds().1 < -150.0 {
604            return Err(FpError::Underflow(repr.sign()));
605        }
606        let odd = convert_base_odd::<B>(repr, 32);
607        // See `convert_to_f64`: unified range check on the base-2 `odd`, before the significand
608        // rounding. f32::MAX = (2^24 − 1) × 2^104 ≈ 2^128.
609        let (lb, ub) = odd.log2_bounds();
610        if lb > 128.0 || (ub >= 128.0 && odd.abs_cmp(&(UBig::from(0xFFFFFFu64) << 104)).is_gt()) {
611            return Err(FpError::Overflow(odd.sign()));
612        }
613        let bits = significand_bits(&odd, 24, -149);
614        let rounded = Context::<R>::new(bits).repr_round(odd);
615        match rounded {
616            Exact(v) => v.into_f32_internal(),
617            Inexact(v, e) => match v.into_f32_internal()? {
618                Exact(f) => Ok(Inexact(f, e)),
619                Inexact(f, e2) => Ok(Inexact(f, e2)),
620            },
621        }
622    }
623
624    // Convert the [Repr] from base B to base NewB, with the precision under the target base from this context.
625    #[allow(non_upper_case_globals)]
626    fn convert_base<const B: Word, const NewB: Word>(
627        &self,
628        repr: Repr<B>,
629        mut cache: Option<&mut ConstCache>,
630    ) -> Rounded<Repr<NewB>> {
631        // shortcut if NewB is the same as B
632        if NewB == B {
633            return Exact(Repr {
634                significand: repr.significand,
635                exponent: repr.exponent,
636            });
637        }
638
639        // shortcut for infinities, no rounding happens but the result is inexact
640        if repr.is_infinite() {
641            return Inexact(
642                Repr {
643                    significand: repr.significand,
644                    exponent: repr.exponent,
645                },
646                Rounding::NoOp,
647            );
648        }
649
650        if NewB > B {
651            // shortcut if NewB is a power of B
652            let n = ilog_exact(NewB, B);
653            if n > 1 {
654                let (exp, rem) = repr.exponent.div_rem_euclid(n as isize);
655                let signif = repr.significand * B.pow(rem as u32);
656                let repr = Repr::new(signif, exp);
657                return self.repr_round(repr);
658            }
659        } else {
660            // shortcut if B is a power of NewB
661            let n = ilog_exact(B, NewB);
662            if n > 1 {
663                let exp = match repr.exponent.checked_mul(n as isize) {
664                    Some(e) => e,
665                    None => return converted_overflow_repr::<NewB>(repr.exponent > 0, repr.sign()),
666                };
667                // The value is exactly representable in the new base, but its significand can
668                // still be wider than the target precision (e.g. the single base-4 digit 3 is
669                // the two-digit binary 0b11) — round it like every other path does.
670                return self.repr_round(Repr::new(repr.significand, exp));
671            }
672        }
673
674        // Shortcut: when B and NewB share common factors, factor out the common part.
675        // B = NewB^a * r where gcd(r, NewB) = 1, so B^exp = NewB^(a*exp) * r^exp.
676        // For positive exponents the result is always exact (integer multiplication).
677        // For negative exponents, exact only when r^|exp| divides the significand.
678        let (a, r) = factor_base(B, NewB);
679        if a > 0 && r > 1 {
680            if repr.exponent >= 0 {
681                let sign = repr.sign();
682                let r_exp = UBig::from_word(r).pow(repr.exponent as usize);
683                let significand = repr.significand * r_exp;
684                let exp = match (a as isize).checked_mul(repr.exponent) {
685                    Some(e) => e,
686                    None => return converted_overflow_repr::<NewB>(true, sign),
687                };
688                let new_repr = Repr::<NewB>::new(significand, exp);
689                return self.repr_round(new_repr);
690            } else {
691                let r_exp: IBig = UBig::from_word(r).pow((-repr.exponent) as usize).into();
692                if let Some(quot) = (&repr.significand).div_exact(r_exp, &()) {
693                    // the quotient's sign is the dividend's (r_exp > 0)
694                    let exp = match (a as isize).checked_mul(repr.exponent) {
695                        Some(e) => e,
696                        None => return converted_overflow_repr::<NewB>(false, quot.sign()),
697                    };
698                    let new_repr = Repr::<NewB>::new(quot, exp);
699                    return self.repr_round(new_repr);
700                }
701            }
702        }
703
704        // When NewB is a multiple of B: compute significand * B^exp directly
705        // as an integer, then express in base NewB.
706        if NewB % B == 0 && repr.exponent >= 0 {
707            let signif = repr.significand * Repr::<B>::BASE.pow(repr.exponent as usize);
708            let new_repr = Repr::<NewB>::new(signif, 0);
709            return self.repr_round(new_repr);
710        }
711
712        // if the base cannot be converted losslessly, the precision must be set
713        if self.precision == 0 {
714            panic_unlimited_precision();
715        }
716
717        // choose a exponent threshold such that number with exponent smaller than this value
718        // will be converted by directly evaluating the power. The threshold here is chosen such
719        // that the power under base 10 will fit in a double word.
720        const THRESHOLD_SMALL_EXP: isize = (Word::BITS as f32 * 0.60206) as isize; // word bits * 2 / log2(10)
721        if repr.exponent.abs() <= THRESHOLD_SMALL_EXP {
722            // if the exponent is small enough, directly evaluate the exponent
723            if repr.exponent >= 0 {
724                let signif = repr.significand * Repr::<B>::BASE.pow(repr.exponent as usize);
725                // The exact integer may carry more digits than the target precision allows
726                // (e.g. 2·2³ = 16 is the three-digit base-3 number 121), so it still goes
727                // through the rounding step.
728                self.repr_round(Repr::new(signif, 0))
729            } else {
730                let den: Repr<NewB> =
731                    Repr::new(Repr::<B>::BASE.pow(-repr.exponent as usize).into(), 0);
732                // `repr_div_any_width` bounds an over-wide dividend itself (exactly — the
733                // dropped low digits are kept as rounding information), so no pre-shrinking
734                // here: rounding the dividend before the division would lie about exactness
735                // and could break tie decisions.
736                let num: Repr<NewB> = Repr::new(repr.significand, 0);
737                match self.repr_div_any_width(num, den) {
738                    Ok(v) => v.map(|r: Repr<NewB>| Repr {
739                        significand: r.significand,
740                        exponent: r.exponent,
741                    }),
742                    Err(FpError::Overflow(sign)) => {
743                        Inexact(Repr::<NewB>::infinity_with_sign(sign), Rounding::NoOp)
744                    }
745                    Err(FpError::Underflow(sign)) => {
746                        Inexact(Repr::<NewB>::zero_with_sign(sign), Rounding::NoOp)
747                    }
748                    Err(_) => unreachable!(),
749                }
750            }
751        } else {
752            // if the exponent is large, then we first estimate the result exponent as floor(exponent * log(B) / log(NewB)),
753            // then the fractional part is multiplied with the original significand
754            let work_context = Context::<R>::new(2 * self.precision); // double the precision to get the precise logarithm
755                                                                      // ln(old base) and ln(new base) — near-correct is sufficient for the exponent estimate,
756                                                                      // and using the near-correct `ln_compute`/`ln_base` (R: Round) keeps base conversion
757                                                                      // off the `ErrorBounds` bound. Both are computed in base NewB so the euclidean division
758                                                                      // has matching bases.
759            let new_exp = repr.exponent
760                * work_context
761                    .ln_compute::<NewB>(
762                        &Repr::new(Repr::<B>::BASE.into(), 0),
763                        work_context.precision,
764                        false,
765                        reborrow_cache(&mut cache),
766                    )
767                    .expect("ln(BASE) cannot range-error (the base fits the exponent range)")
768                    .to_value_radius::<R>(&work_context)
769                    .0;
770            let (exponent, rem) =
771                new_exp.div_rem_euclid(work_context.ln_base::<NewB>(reborrow_cache(&mut cache)));
772            let exponent_sign = exponent.sign();
773            let exponent: isize = match exponent.try_into() {
774                Ok(v) => v,
775                Err(_) => {
776                    return converted_overflow_repr::<NewB>(
777                        exponent_sign == Sign::Positive,
778                        repr.sign(),
779                    );
780                }
781            };
782            // exp(fractional exponent) — near-correct is sufficient (it scales the significand),
783            // so use `exp_compute` (R: Round) and stay off the `ErrorBounds` bound.
784            let n = 1usize << (work_context.precision.bit_len() / 2);
785            let exp_rem = work_context
786                .exp_compute::<NewB>(
787                    &rem.repr,
788                    work_context.precision,
789                    false,
790                    n,
791                    reborrow_cache(&mut cache),
792                )
793                .expect("exp(reduced rem) cannot overflow (|rem| < B^-n)")
794                .mid;
795            let significand = &repr.significand * exp_rem.significand();
796            let repr = Repr::new(significand, exponent + exp_rem.exponent());
797            self.repr_round(repr)
798        }
799    }
800}
801
802impl<const B: Word> Repr<B> {
803    // this method requires that the representation is already rounded to 24 binary bits
804    fn into_f32_internal(self) -> FpResult<f32> {
805        assert!(B == 2);
806        debug_assert!(self.is_finite());
807        debug_assert!(self.significand.bit_len() <= 24);
808
809        let sign = self.sign();
810        if self.is_neg_zero() {
811            // encode() would drop the sign of -0; preserve it exactly
812            return Ok(Exact(sign * 0f32));
813        }
814        let man24: i32 = self.significand.try_into().unwrap();
815        match f32::encode(man24, self.exponent as i16) {
816            Exact(v) => Ok(Exact(v)),
817            Inexact(v, _) if v.is_infinite() => Err(FpError::Overflow(sign)),
818            Inexact(0.0, _) => Err(FpError::Underflow(sign)),
819            Inexact(v, _) => Ok(Inexact(v, Rounding::NoOp)),
820        }
821    }
822
823    /// Convert the float number representation to a [f32] with the default IEEE 754 rounding mode.
824    ///
825    /// The default IEEE 754 rounding mode is [HalfEven] (rounding to nearest, ties to even). To convert
826    /// the float number with a specific rounding mode, please use [FBig::to_f32].
827    ///
828    /// # Examples
829    ///
830    /// ```
831    /// # use dashu_base::Approximation::*;
832    /// # use dashu_float::{Repr, round::Rounding::*};
833    /// assert_eq!(Repr::<2>::one().to_f32(), Exact(1.0));
834    /// assert_eq!(Repr::<10>::infinity().to_f32(), Inexact(f32::INFINITY, NoOp));
835    /// ```
836    #[inline]
837    pub fn to_f32(&self) -> Rounded<f32> {
838        match Context::<HalfEven>::convert_to_f32(self.clone()) {
839            Ok(rounded) => rounded,
840            Err(err) => f32_directed_endpoint::<HalfEven>(err),
841        }
842    }
843
844    // this method requires that the representation is already rounded to 53 binary bits
845    fn into_f64_internal(self) -> FpResult<f64> {
846        assert!(B == 2);
847        debug_assert!(self.is_finite());
848        debug_assert!(self.significand.bit_len() <= 53);
849
850        let sign = self.sign();
851        if self.is_neg_zero() {
852            // encode() would drop the sign of -0; preserve it exactly
853            return Ok(Exact(sign * 0f64));
854        }
855        let man53: i64 = self.significand.try_into().unwrap();
856        match f64::encode(man53, self.exponent as i16) {
857            Exact(v) => Ok(Exact(v)),
858            Inexact(v, _) if v.is_infinite() => Err(FpError::Overflow(sign)),
859            Inexact(0.0, _) => Err(FpError::Underflow(sign)),
860            Inexact(v, _) => Ok(Inexact(v, Rounding::NoOp)),
861        }
862    }
863
864    /// Convert the float number representation to a [f64] with the default IEEE 754 rounding mode.
865    ///
866    /// The default IEEE 754 rounding mode is [HalfEven] (rounding to nearest, ties to even). To convert
867    /// the float number with a specific rounding mode, please use [FBig::to_f64].
868    ///
869    /// # Examples
870    ///
871    /// ```
872    /// # use dashu_base::Approximation::*;
873    /// # use dashu_float::{Repr, round::Rounding::*};
874    /// assert_eq!(Repr::<2>::one().to_f64(), Exact(1.0));
875    /// assert_eq!(Repr::<10>::infinity().to_f64(), Inexact(f64::INFINITY, NoOp));
876    /// ```
877    #[inline]
878    pub fn to_f64(&self) -> Rounded<f64> {
879        match Context::<HalfEven>::convert_to_f64(self.clone()) {
880            Ok(rounded) => rounded,
881            Err(err) => f64_directed_endpoint::<HalfEven>(err),
882        }
883    }
884
885    /// Convert the float number representation to a [IBig].
886    ///
887    /// The fractional part is always rounded to zero. To convert with other rounding modes,
888    /// please use [FBig::to_int()].
889    ///
890    /// # Warning
891    ///
892    /// If the float number has a very large exponent, it will be evaluated and result
893    /// in allocating an huge integer and it might eat up all your memory.
894    ///
895    /// To get a rough idea of how big the number is, it's recommended to use [EstimatedLog2].
896    ///
897    /// # Examples
898    ///
899    /// ```
900    /// # use dashu_base::Approximation::*;
901    /// # use dashu_int::IBig;
902    /// # use dashu_float::{Repr, round::Rounding::*};
903    /// assert_eq!(Repr::<2>::neg_one().to_int(), Exact(IBig::NEG_ONE));
904    /// ```
905    ///
906    /// # Panics
907    ///
908    /// Panics if the number is infinte.
909    pub fn to_int(&self) -> Rounded<IBig> {
910        assert_finite(self);
911
912        if self.exponent >= 0 {
913            // the number is already an integer
914            Exact(shl_digits::<B>(&self.significand, self.exponent as usize))
915        } else if self.smaller_than_one() {
916            // the number is definitely smaller than
917            Inexact(IBig::ZERO, Rounding::NoOp)
918        } else {
919            let int = shr_digits::<B>(&self.significand, (-self.exponent) as usize);
920            Inexact(int, Rounding::NoOp)
921        }
922    }
923}
924
925impl<const B: Word> From<UBig> for Repr<B> {
926    #[inline]
927    fn from(n: UBig) -> Self {
928        Self::new(n.into(), 0)
929    }
930}
931impl<R: Round, const B: Word> From<UBig> for FBig<R, B> {
932    #[inline]
933    fn from(n: UBig) -> Self {
934        Self::from_parts(n.into(), 0)
935    }
936}
937
938impl<const B: Word> From<IBig> for Repr<B> {
939    #[inline]
940    fn from(n: IBig) -> Self {
941        Self::new(n, 0)
942    }
943}
944impl<R: Round, const B: Word> From<IBig> for FBig<R, B> {
945    #[inline]
946    fn from(n: IBig) -> Self {
947        Self::from_parts(n, 0)
948    }
949}
950
951impl<R: Round, const B: Word> TryFrom<FBig<R, B>> for IBig {
952    type Error = ConversionError;
953
954    #[inline]
955    fn try_from(value: FBig<R, B>) -> Result<Self, Self::Error> {
956        if value.repr.is_infinite() {
957            Err(ConversionError::OutOfBounds)
958        } else if value.repr.significand.is_zero() {
959            // A zero significand is integer zero regardless of exponent. This also
960            // accepts IEEE-754 signed zero, whose sign is carried by a -1 exponent
961            // sentinel (not the significand); it is treated as plain 0. The zero
962            // must be handled here rather than in the `else` branch below, which
963            // shifts by `exponent as usize` and would underflow on the -1 sentinel.
964            Ok(value.repr.significand)
965        } else if value.repr.exponent < 0 {
966            Err(ConversionError::LossOfPrecision)
967        } else {
968            let mut int = value.repr.significand;
969            shl_digits_in_place::<B>(&mut int, value.repr.exponent as usize);
970            Ok(int)
971        }
972    }
973}
974
975impl<R: Round, const B: Word> TryFrom<FBig<R, B>> for UBig {
976    type Error = ConversionError;
977
978    #[inline]
979    fn try_from(value: FBig<R, B>) -> Result<Self, Self::Error> {
980        let int: IBig = value.try_into()?;
981        int.try_into()
982    }
983}
984
985macro_rules! fbig_unsigned_conversions {
986    ($($t:ty)*) => {$(
987        impl<const B: Word> From<$t> for Repr<B> {
988            #[inline]
989            fn from(value: $t) -> Repr<B> {
990                UBig::from(value).into()
991            }
992        }
993        impl<R: Round, const B: Word> From<$t> for FBig<R, B> {
994            #[inline]
995            fn from(value: $t) -> FBig<R, B> {
996                UBig::from(value).into()
997            }
998        }
999
1000        impl<const B: Word> TryFrom<Repr<B>> for $t {
1001            type Error = ConversionError;
1002
1003            fn try_from(value: Repr<B>) -> Result<Self, Self::Error> {
1004                if value.sign() == Sign::Negative || value.is_infinite() {
1005                    Err(ConversionError::OutOfBounds)
1006                } else {
1007                    let (log2_lb, _) = value.log2_bounds();
1008                    if log2_lb >= <$t>::BITS as f32 {
1009                        Err(ConversionError::OutOfBounds)
1010                    } else if value.exponent < 0 {
1011                        Err(ConversionError::LossOfPrecision)
1012                    } else {
1013                        shl_digits::<B>(&value.significand, value.exponent as usize).try_into()
1014                    }
1015                }
1016            }
1017        }
1018        impl<R: Round, const B: Word> TryFrom<FBig<R, B>> for $t {
1019            type Error = ConversionError;
1020
1021            #[inline]
1022            fn try_from(value: FBig<R, B>) -> Result<Self, Self::Error> {
1023                value.repr.try_into()
1024            }
1025        }
1026    )*};
1027}
1028fbig_unsigned_conversions!(u8 u16 u32 u64 u128 usize);
1029
1030macro_rules! fbig_signed_conversions {
1031    ($($t:ty)*) => {$(
1032        impl<R: Round, const B: Word> From<$t> for FBig<R, B> {
1033            #[inline]
1034            fn from(value: $t) -> FBig<R, B> {
1035                IBig::from(value).into()
1036            }
1037        }
1038
1039        impl<R: Round, const B: Word> TryFrom<FBig<R, B>> for $t {
1040            type Error = ConversionError;
1041
1042            fn try_from(value: FBig<R, B>) -> Result<Self, Self::Error> {
1043                if value.repr.is_infinite() {
1044                    Err(ConversionError::OutOfBounds)
1045                } else {
1046                    let (log2_lb, _) = value.repr.log2_bounds();
1047                    if log2_lb >= <$t>::BITS as f32 {
1048                        Err(ConversionError::OutOfBounds)
1049                    } else if value.repr.exponent < 0 {
1050                        Err(ConversionError::LossOfPrecision)
1051                    } else {
1052                        shl_digits::<B>(&value.repr.significand, value.repr.exponent as usize).try_into()
1053                    }
1054                }
1055            }
1056        }
1057    )*};
1058}
1059fbig_signed_conversions!(i8 i16 i32 i64 i128 isize);
1060
1061// The directed saturation endpoint for an out-of-range f32/f64 result, chosen from the `FpError`
1062// returned by `into_f*_internal`. Overflow saturates to ±MAX or ±∞ per the mode (outward modes
1063// reach ±∞; toward-zero/opposite/nearest saturate to the largest finite); underflow saturates to
1064// ±0 or the smallest subnormal of that sign. `round_low_part`'s AddOne/SubOne verdict on a
1065// same-sign residual is exactly the outward-vs-inward decision; only its directional verdict is
1066// used. This is the single place that picks the endpoint, shared by `to_f32`/`to_f64` (Repr uses
1067// HalfEven, FBig uses its own mode).
1068macro_rules! impl_float_directed_endpoint {
1069    (
1070        $fn:ident, $t:ty, $max:expr, $min:expr, $inf:expr, $neg_inf:expr,
1071        $smallest_sub:expr, $neg_smallest_sub:expr
1072    ) => {
1073        fn $fn<R: Round>(err: FpError) -> Rounded<$t> {
1074            match err {
1075                FpError::Overflow(sign) => {
1076                    let adj = if sign == Sign::Positive {
1077                        R::round_low_part(&IBig::ONE, Sign::Positive, || {
1078                            core::cmp::Ordering::Greater
1079                        })
1080                    } else {
1081                        R::round_low_part(&IBig::NEG_ONE, Sign::Negative, || {
1082                            core::cmp::Ordering::Greater
1083                        })
1084                    };
1085                    Inexact(
1086                        match (sign, adj) {
1087                            (Sign::Positive, AddOne) => $inf,
1088                            (Sign::Positive, _) => $max,
1089                            (Sign::Negative, SubOne) => $neg_inf,
1090                            (Sign::Negative, _) => $min,
1091                        },
1092                        adj,
1093                    )
1094                }
1095                FpError::Underflow(sign) => {
1096                    let adj = if sign == Sign::Positive {
1097                        R::round_low_part(&IBig::ZERO, Sign::Positive, || core::cmp::Ordering::Less)
1098                    } else {
1099                        R::round_low_part(&IBig::ZERO, Sign::Negative, || core::cmp::Ordering::Less)
1100                    };
1101                    Inexact(
1102                        match (sign, adj) {
1103                            (Sign::Positive, AddOne) => $smallest_sub, // smallest positive subnormal
1104                            (Sign::Positive, _) => 0.0,
1105                            (Sign::Negative, SubOne) => $neg_smallest_sub,
1106                            (Sign::Negative, _) => -0.0,
1107                        },
1108                        adj,
1109                    )
1110                }
1111                // `into_f*_internal` only returns Overflow/Underflow; the infinite-input case is
1112                // handled by `convert_to_f*` (returning `Ok`) before this is reached.
1113                _ => unreachable!("convert_to_f* only returns Overflow/Underflow here"),
1114            }
1115        }
1116    };
1117}
1118impl_float_directed_endpoint!(
1119    f32_directed_endpoint,
1120    f32,
1121    f32::MAX,
1122    f32::MIN,
1123    f32::INFINITY,
1124    f32::NEG_INFINITY,
1125    f32::from_bits(1),
1126    f32::from_bits(0x8000_0001)
1127);
1128impl_float_directed_endpoint!(
1129    f64_directed_endpoint,
1130    f64,
1131    f64::MAX,
1132    f64::MIN,
1133    f64::INFINITY,
1134    f64::NEG_INFINITY,
1135    f64::from_bits(1),
1136    f64::from_bits(0x8000_0000_0000_0001)
1137);
1138
1139macro_rules! impl_from_fbig_for_float {
1140    ($t:ty, $convert:ident) => {
1141        impl TryFrom<Repr<2>> for $t {
1142            type Error = ConversionError;
1143
1144            #[inline]
1145            fn try_from(value: Repr<2>) -> Result<Self, Self::Error> {
1146                if value.is_infinite() {
1147                    return Err(ConversionError::LossOfPrecision);
1148                }
1149                // Range detection is shared with `to_f32`/`to_f64` via `convert_to_f*`: it returns
1150                // `Err(Overflow)` for a value beyond the largest finite `$t`, so `OutOfBounds` is
1151                // reported the same way under every rounding mode (Repr uses HalfEven here).
1152                match Context::<HalfEven>::$convert(value) {
1153                    Ok(Exact(v)) => Ok(v),
1154                    Ok(Inexact(_, _)) => Err(ConversionError::LossOfPrecision),
1155                    Err(FpError::Overflow(_)) => Err(ConversionError::OutOfBounds),
1156                    Err(FpError::Underflow(_)) => Err(ConversionError::LossOfPrecision),
1157                    Err(_) => unreachable!(),
1158                }
1159            }
1160        }
1161
1162        impl<R: Round> TryFrom<FBig<R, 2>> for $t {
1163            type Error = ConversionError;
1164
1165            #[inline]
1166            fn try_from(value: FBig<R, 2>) -> Result<Self, Self::Error> {
1167                if value.repr.is_infinite() {
1168                    return Err(ConversionError::LossOfPrecision);
1169                }
1170                // A value beyond the largest finite `$t` is out of range whatever the rounding mode:
1171                // the mode only selects the saturation endpoint (±MAX vs ±∞), and `convert_to_f*`
1172                // reports that range condition as `Err(Overflow)` regardless of mode — so
1173                // `Err(OutOfBounds)` reliably means "beyond the finite range", unlike the old
1174                // result-infiniteness check (which flipped LossOfPrecision/OutOfBounds with the mode).
1175                match Context::<R>::$convert(value.repr) {
1176                    Ok(Exact(v)) => Ok(v),
1177                    Ok(Inexact(_, _)) => Err(ConversionError::LossOfPrecision),
1178                    Err(FpError::Overflow(_)) => Err(ConversionError::OutOfBounds),
1179                    Err(FpError::Underflow(_)) => Err(ConversionError::LossOfPrecision),
1180                    Err(_) => unreachable!(),
1181                }
1182            }
1183        }
1184    };
1185}
1186impl_from_fbig_for_float!(f32, convert_to_f32);
1187impl_from_fbig_for_float!(f64, convert_to_f64);
1188
1189#[cfg(test)]
1190mod tests {
1191    use super::*;
1192    use crate::repr::Repr;
1193    use crate::round::mode::{HalfEven, Zero};
1194
1195    // Regression tests for issue #100: base conversion used to return unreduced significands
1196    // from its exact-conversion shortcuts, and pre-rounded the dividend of its division path
1197    // (lying about exactness).
1198    #[test]
1199    fn test_with_base_reduces_and_flags() {
1200        // 2.1 -> base 2 @ p1: 21/10 has no finite binary form, so the conversion is inexact;
1201        // the old division path pre-rounded 21 to 20, and 20/10 = 2 divided exactly.
1202        let r = FBig::<Zero, 10>::from_parts(IBig::from(21), -1).with_base_and_precision::<2>(1);
1203        assert!(matches!(r, Inexact(..)), "2.1 -> base 2 @ p1 must be inexact");
1204
1205        // 3.1 -> base 2 @ p1: the significand 11₂ must be reduced to one digit.
1206        let v = FBig::<Zero, 10>::from_parts(IBig::from(31), -1)
1207            .with_base_and_precision::<2>(1)
1208            .value();
1209        assert!(v.repr().digits() <= v.precision());
1210
1211        // 16 (2·2³) -> base 3 @ p1: the exact conversion produces 121₃ — three digits, which
1212        // must be rounded (Zero -> 1·3² = 9, since 16 lies between 9 and 18).
1213        let v = FBig::<Zero, 2>::from_parts(IBig::from(2), 3)
1214            .with_base_and_precision::<3>(1)
1215            .value();
1216        assert_eq!(v.repr(), &Repr::<3>::new(IBig::from(1), 2));
1217
1218        // 3 (base 4) -> base 2 @ p1: the single base-4 digit 3 is the two-digit binary 0b11,
1219        // so the "B is a power of NewB" shortcut must still round (Zero -> 1·2¹ = 2).
1220        let v = FBig::<Zero, 4>::from_parts(IBig::from(3), 0)
1221            .with_base_and_precision::<2>(1)
1222            .value();
1223        assert_eq!(v.repr(), &Repr::<2>::new(IBig::from(1), 1));
1224    }
1225
1226    // An exact conversion whose result fits the precision stays Exact through the shortcuts.
1227    #[test]
1228    fn test_with_base_exact_fits() {
1229        // 1.5 (base 10) -> base 2 @ p2: 15/100·... = 3/2 divides exactly onto 11·2^-1.
1230        let r =
1231            FBig::<HalfEven, 10>::from_parts(IBig::from(15), -1).with_base_and_precision::<2>(2);
1232        assert!(matches!(r, Exact(..)));
1233        assert_eq!(r.value().repr(), &Repr::<2>::new(IBig::from(3), -1));
1234
1235        // 4 (base 2) -> base 4: conversion to a power of the base is exact (4 = 1·4¹).
1236        let r = FBig::<HalfEven, 2>::from_parts(IBig::from(1), 2).with_base::<4>();
1237        assert!(matches!(r, Exact(..)));
1238        assert_eq!(r.value().repr(), &Repr::<4>::new(IBig::from(1), 1));
1239    }
1240
1241    // Directed overflow must reach the endpoint for a value whose *most*-significant bit straddles
1242    // f32::MAX (not only for values whose lsb exponent is ≥ 128). `3·2¹²⁷` ≈ 1.5·2¹²⁸ overflows
1243    // f32::MAX = (2 − 2⁻²³)·2¹²⁷, yet its lsb exponent is 127 — the old exponent-gated branch fell
1244    // through to `encode`, which saturates to ±∞ mode-blindly.
1245    #[test]
1246    fn f32_directed_overflow_at_msb_boundary() {
1247        use crate::round::mode::{Down, Up};
1248        // f32: 3·2^127 under Zero/Down -> f32::MAX, under Up/Away -> +∞.
1249        let zero = FBig::<Zero, 2>::from_parts(3.into(), 127);
1250        let down = FBig::<Down, 2>::from_parts(3.into(), 127);
1251        let up = FBig::<Up, 2>::from_parts(3.into(), 127);
1252        assert_eq!(zero.to_f32().value().to_bits(), 0x7f7fffff); // f32::MAX
1253        assert_eq!(down.to_f32().value().to_bits(), 0x7f7fffff);
1254        assert!(up.to_f32().value().is_infinite() && up.to_f32().value().is_sign_positive());
1255        // negative mirror
1256        let nzero = FBig::<Zero, 2>::from_parts((-3).into(), 127);
1257        assert_eq!(nzero.to_f32().value().to_bits(), 0xff7fffff); // f32::MIN
1258
1259        // f64: 3·2^1023 overflows f64::MAX; lsb exponent 1023 < 1024.
1260        let z64 = FBig::<Zero, 2>::from_parts(3.into(), 1023);
1261        assert_eq!(z64.to_f64().value().to_bits(), 0x7fefffffffffffff); // f64::MAX
1262        let u64 = FBig::<Up, 2>::from_parts(3.into(), 1023);
1263        assert!(u64.to_f64().value().is_infinite() && u64.to_f64().value().is_sign_positive());
1264    }
1265
1266    // Directed underflow must reach the endpoint through the `encode` path too (not only for the
1267    // extreme exponents caught by the old explicit branch). `2⁻¹⁶⁰` is below the smallest subnormal;
1268    // under Up a positive value must round up to `2⁻¹⁴⁹`, but `encode` returns ±0 mode-blindly.
1269    #[test]
1270    fn f32_directed_underflow_through_encode() {
1271        use crate::round::mode::{Away, Down, Up};
1272        // f32: 2^-160 under Up/Away -> smallest +subnormal, under Zero/Down -> +0.
1273        let up = FBig::<Up, 2>::from_parts(IBig::ONE, -160);
1274        let away = FBig::<Away, 2>::from_parts(IBig::ONE, -160);
1275        let zero = FBig::<Zero, 2>::from_parts(IBig::ONE, -160);
1276        let down = FBig::<Down, 2>::from_parts(IBig::ONE, -160);
1277        assert_eq!(up.to_f32().value().to_bits(), 0x00000001); // smallest +subnormal
1278        assert_eq!(away.to_f32().value().to_bits(), 0x00000001);
1279        assert_eq!(zero.to_f32().value().to_bits(), 0x0);
1280        assert_eq!(down.to_f32().value().to_bits(), 0x0);
1281        // negative: Down/Away -> smallest -subnormal, Zero/Up -> -0.
1282        let ndown = FBig::<Down, 2>::from_parts(-IBig::ONE, -160);
1283        assert_eq!(ndown.to_f32().value().to_bits(), 0x80000001);
1284
1285        // f64: 2^-1100 under Up -> smallest +subnormal.
1286        let u64 = FBig::<Up, 2>::from_parts(IBig::ONE, -1100);
1287        assert_eq!(u64.to_f64().value().to_bits(), 0x0000_0000_0000_0001);
1288    }
1289
1290    // A decimal FBig whose value exceeds f32::MAX must saturate to the largest *finite* f32 under
1291    // toward-zero (Zero), not +∞ — directed overflow picks the endpoint per mode.
1292    #[test]
1293    fn f32_overflow_saturates_to_max_under_toward_zero() {
1294        use crate::round::mode::Up;
1295        // value ≈ 1.8e67 ≫ f32::MAX
1296        let sig: IBig = "18113714167384154970503568309331051197800435559900844351020490293248000000000000000000000000000000000000000000000000000000000000000000000040081793412673420422647135518538018600544982168035325793404432580816367361955984101780462905766719198411690240".parse().unwrap();
1297        let down = FBig::<Zero, 10>::from_parts(sig.clone(), -180);
1298        assert_eq!(down.to_f32().value().to_bits(), 0x7f7fffff); // f32::MAX, not +∞
1299                                                                 // The same value toward +∞ does reach +∞.
1300        let up = FBig::<Up, 10>::from_parts(sig, -180);
1301        assert!(up.to_f32().value().is_infinite());
1302    }
1303
1304    // A wide decimal significand near an f32 subnormal midpoint must round to the correct
1305    // neighbor. The near-correct base-conversion logarithm previously landed on the wrong side of a
1306    // 32-bit midpoint, producing a result 1 ULP off (0x00040008 instead of 0x00040007).
1307    #[test]
1308    fn f32_decimal_subnormal_rounds_correctly() {
1309        let v = FBig::<HalfEven, 10>::from_parts(
1310            "367352494370447282365772889742681992006459962834329374643515088008836389924495676300982092025765783512749250624297822169761305238359955010699895018824154119671"
1311                .parse()
1312                .unwrap(),
1313            -198,
1314        );
1315        assert_eq!(v.to_f32().value().to_bits(), 0x00040007);
1316    }
1317
1318    // A positive value below the smallest subnormal must round *up* to that smallest subnormal
1319    // under Up/Away (and to ±0 under toward-zero/opposite/nearest) — the underflow path used to
1320    // return a mode-blind signed zero, which is not an upper bound for a positive value under Up.
1321    #[test]
1322    fn f64_directed_underflow_below_smallest_subnormal() {
1323        use crate::round::mode::Down;
1324        // tiny positive binary value, far below 2^-1074
1325        let up = FBig::<crate::round::mode::Up, 2>::from_parts(IBig::ONE, -20000);
1326        let away = FBig::<crate::round::mode::Away, 2>::from_parts(IBig::ONE, -20000);
1327        let zero = FBig::<Zero, 2>::from_parts(IBig::ONE, -20000);
1328        let down = FBig::<Down, 2>::from_parts(IBig::ONE, -20000);
1329        assert_eq!(up.to_f64().value().to_bits(), 0x0000_0000_0000_0001); // smallest +subnormal
1330        assert_eq!(away.to_f64().value().to_bits(), 0x0000_0000_0000_0001);
1331        assert_eq!(zero.to_f64().value().to_bits(), 0x0); // +0
1332        assert_eq!(down.to_f64().value().to_bits(), 0x0);
1333
1334        // tiny negative: Down/Away -> smallest -subnormal, Zero/Up -> -0
1335        let ndown = FBig::<Down, 2>::from_parts(-IBig::ONE, -20000);
1336        let nup = FBig::<crate::round::mode::Up, 2>::from_parts(-IBig::ONE, -20000);
1337        assert_eq!(ndown.to_f64().value().to_bits(), 0x8000_0000_0000_0001); // smallest -subnormal
1338        assert_eq!(nup.to_f64().value().to_bits(), 0x8000_0000_0000_0000); // -0
1339    }
1340
1341    // A wide-significand *decimal* value far below ½·MIN_SUBNORMAL (exponent −20000). The base
1342    // conversion's internal `exp` underflows for such a catastrophically tiny value, so without the
1343    // source-`log2_bounds` short-circuit the converted magnitude is wrong and `to_f64` returned a
1344    // spurious finite subnormal (≈2^-593) instead of the directed underflow endpoint. The binary
1345    // test above doesn't hit this — it skips base conversion entirely.
1346    #[test]
1347    fn f64_decimal_wide_significand_underflow() {
1348        use crate::round::mode::{Down, Up};
1349        let wide = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234"
1350            .parse::<IBig>()
1351            .unwrap();
1352        for prec in [20usize, 50, 100, 500] {
1353            let he = FBig::<HalfEven, 10>::from_parts(wide.clone(), -20000)
1354                .with_precision(prec)
1355                .value();
1356            let up = FBig::<Up, 10>::from_parts(wide.clone(), -20000)
1357                .with_precision(prec)
1358                .value();
1359            let dn = FBig::<Down, 10>::from_parts(wide.clone(), -20000)
1360                .with_precision(prec)
1361                .value();
1362            // positive value: nearest/Down -> +0, Up -> smallest positive subnormal
1363            assert_eq!(he.to_f64().value().to_bits(), 0x0, "HalfEven @ prec {prec}");
1364            assert_eq!(dn.to_f64().value().to_bits(), 0x0, "Down @ prec {prec}");
1365            assert_eq!(up.to_f64().value().to_bits(), 0x0000_0000_0000_0001, "Up @ prec {prec}");
1366            // f32 mirror: |x| < 2^-150 = ½·MIN_SUBNORMAL -> +0 / smallest +subnormal
1367            assert_eq!(he.to_f32().value().to_bits(), 0x0u32, "f32 HalfEven @ prec {prec}");
1368            assert_eq!(up.to_f32().value().to_bits(), 0x0000_0001u32, "f32 Up @ prec {prec}");
1369
1370            // negative value: nearest/Up -> -0, Down -> smallest negative subnormal
1371            let neg = FBig::<HalfEven, 10>::from_parts(-wide.clone(), -20000)
1372                .with_precision(prec)
1373                .value();
1374            let ndn = FBig::<Down, 10>::from_parts(-wide.clone(), -20000)
1375                .with_precision(prec)
1376                .value();
1377            assert_eq!(
1378                neg.to_f64().value().to_bits(),
1379                0x8000_0000_0000_0000,
1380                "neg HalfEven @ prec {prec}"
1381            );
1382            assert_eq!(
1383                ndn.to_f64().value().to_bits(),
1384                0x8000_0000_0000_0001,
1385                "neg Down @ prec {prec}"
1386            );
1387        }
1388    }
1389
1390    #[test]
1391    fn ibig_try_from_accepts_signed_zero() {
1392        // IEEE-754 signed zero (sign encoded in a -1 exponent sentinel) is plain 0.
1393        let neg_zero = FBig::<HalfAway, 10>::new(Repr::neg_zero(), Context::new(8));
1394        assert_eq!(IBig::try_from(neg_zero), Ok(IBig::from(0)));
1395
1396        // positive zero already worked, and still does
1397        let pos_zero = FBig::<HalfAway, 10>::new(Repr::zero(), Context::new(8));
1398        assert_eq!(IBig::try_from(pos_zero), Ok(IBig::from(0)));
1399
1400        // UBig delegates to the IBig impl, so it accepts signed zero too
1401        let neg_zero = FBig::<HalfAway, 2>::new(Repr::neg_zero(), Context::new(8));
1402        assert_eq!(UBig::try_from(neg_zero), Ok(UBig::from(0u8)));
1403
1404        // a genuine fractional value must still be rejected
1405        let frac = FBig::<HalfAway, 10>::new(Repr::new(IBig::from(1), -1), Context::new(8));
1406        assert_eq!(IBig::try_from(frac), Err(ConversionError::LossOfPrecision));
1407
1408        // a normal integer round-trips exactly
1409        let int_val = FBig::<HalfAway, 10>::new(Repr::new(IBig::from(42), 0), Context::new(8));
1410        assert_eq!(IBig::try_from(int_val), Ok(IBig::from(42)));
1411    }
1412
1413    #[test]
1414    fn with_base_high_precision_no_overflow() {
1415        // Regression for issue #95: converting a high-precision base-2 float to base
1416        // 10 panicked on 32-bit targets ("arithmetic operations with the infinity are
1417        // not allowed!"). The base conversion evaluates exp(r) as `sum^(B^n)` through
1418        // `powi` with a huge exponent (B^n) on a base (sum) very close to 1; `powi`'s
1419        // overflow guard estimated log2(base) with the catastrophically-canceling
1420        // `log2_est`, and the ~1e-4 of f32 noise scaled by the exponent crossed the
1421        // (much smaller on 32-bit) isize threshold, yielding a spurious ±inf that then
1422        // panicked when shifted. See `powi` in exp.rs for the fix.
1423        use crate::round::mode::Zero;
1424        use core::str::FromStr;
1425
1426        // The reporter's input: -1.1111…0011 in binary (578 significant bits), written
1427        // in the hex form dashu accepts for base-2 floats (`0x1.<hex>…`). The value is
1428        // identical to the raw binary literal.
1429        let num = FBig::<Zero, 2>::from_str(
1430            "-0x1.fffdc8d645194a5a95df4be063472d4406dd096339dd7dc2a8527d208b3da7b9e5c36b4f49a7982cb2ad20a4e7e4c016f858fe8cddea011a6d01fe3823189c4ed4f57a7babc331498",
1431        )
1432        .unwrap();
1433
1434        // at the original 578-bit precision the conversion succeeds …
1435        let a = num
1436            .clone()
1437            .with_precision(578)
1438            .value()
1439            .with_base::<10>()
1440            .value();
1441        assert!(a.repr().is_finite());
1442        // … and so does a slightly higher precision (586), which panicked on 32-bit
1443        // (wasm32 / i686). The result matches the value computed on 64-bit. Compared
1444        // by value (FBig equality ignores context) rather than via string formatting,
1445        // so this works under no_std too.
1446        let b = num.with_precision(586).value().with_base::<10>().value();
1447        assert!(b.repr().is_finite());
1448        let expected = FBig::<Zero, 10>::from_str(
1449            "-1.9999661944503703041843468850635057967553124154072485151176192294480158424234268438137612977886891381228704640656094986435381057574477216648567249609280392009533217665484389886",
1450        )
1451        .unwrap();
1452        assert_eq!(b, expected);
1453    }
1454
1455    // Shrinking an *unlimited*-precision value must round its significand down to the
1456    // target precision. Previously the `with_precision` shrink guard compared context
1457    // precisions (`0 > N` is always false), so an unlimited source was never rounded.
1458    #[test]
1459    fn with_precision_from_unlimited_rounds() {
1460        let a =
1461            FBig::<Zero, 2>::from_repr(Repr::<2>::new(IBig::from(0xffffu32), 0), Context::new(0));
1462        let b = a.with_precision(8).value();
1463        assert_eq!(b.precision(), 8);
1464        assert_eq!(b.repr().digits(), 8);
1465        assert_eq!(b.repr().significand(), &IBig::from(0xffu32));
1466    }
1467}