Skip to main content

dashu_float/
convert.rs

1use core::{
2    convert::{TryFrom, TryInto},
3    num::FpCategory,
4};
5
6use dashu_base::{
7    Approximation::*, BitTest, ConversionError, DivRemEuclid, EstimatedLog2, FloatEncoding, Sign,
8    Signed,
9};
10use dashu_int::{IBig, UBig, Word};
11
12use crate::{
13    error::{assert_finite, panic_unlimited_precision, FpError},
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    },
21    utils::{factor_base, ilog_exact, shl_digits, shl_digits_in_place, shr_digits},
22};
23
24impl<R: Round> Context<R> {
25    /// Convert an [IBig] instance to a [FBig] instance with precision
26    /// and rounding given by the context.
27    ///
28    /// # Examples
29    ///
30    /// ```
31    /// # use core::str::FromStr;
32    /// # use dashu_base::ParseError;
33    /// # use dashu_float::DBig;
34    /// use dashu_base::Approximation::*;
35    /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
36    ///
37    /// let context = Context::<HalfAway>::new(2);
38    /// assert_eq!(context.convert_int::<10>((-12).into()), Exact(DBig::from_str("-12")?));
39    /// assert_eq!(
40    ///     context.convert_int::<10>(5678.into()),
41    ///     Inexact(DBig::from_str("5.7e3")?, AddOne)
42    /// );
43    /// # Ok::<(), ParseError>(())
44    /// ```
45    pub fn convert_int<const B: Word>(&self, n: IBig) -> Rounded<FBig<R, B>> {
46        let repr = Repr::<B>::new(n, 0);
47        self.repr_round(repr).map(|v| FBig::new(v, *self))
48    }
49}
50
51macro_rules! impl_from_float_for_fbig {
52    ($t:ty) => {
53        impl TryFrom<$t> for Repr<2> {
54            type Error = ConversionError;
55
56            fn try_from(f: $t) -> Result<Self, Self::Error> {
57                match f.decode() {
58                    Ok((man, exp)) => Ok(if man == 0 && f.is_sign_negative() {
59                        Self::neg_zero()
60                    } else {
61                        Repr::new(man.into(), exp as _)
62                    }),
63                    Err(FpCategory::Infinite) => match f.sign() {
64                        Sign::Positive => Ok(Self::infinity()),
65                        Sign::Negative => Ok(Self::neg_infinity()),
66                    },
67                    _ => Err(ConversionError::OutOfBounds), // NaN
68                }
69            }
70        }
71
72        impl<R: Round> TryFrom<$t> for FBig<R, 2> {
73            type Error = ConversionError;
74
75            fn try_from(f: $t) -> Result<Self, Self::Error> {
76                match f.decode() {
77                    Ok((man, exp)) => {
78                        // preserve the sign of a signed zero (-0.0 -> Repr::neg_zero())
79                        let repr = if man == 0 && f.is_sign_negative() {
80                            Repr::neg_zero()
81                        } else {
82                            Repr::new(man.into(), exp as _)
83                        };
84
85                        // The precision is inferenced from the mantissa, because the mantissa of
86                        // normal float is always normalized. This will produce correct precision
87                        // for subnormal floats
88                        let bits = man.unsigned_abs().bit_len();
89                        let context = Context::new(bits);
90                        Ok(Self::new(repr, context))
91                    }
92                    Err(FpCategory::Infinite) => match f.sign() {
93                        Sign::Positive => Ok(Self::INFINITY),
94                        Sign::Negative => Ok(Self::NEG_INFINITY),
95                    },
96                    _ => Err(ConversionError::OutOfBounds), // NaN
97                }
98            }
99        }
100    };
101}
102
103impl_from_float_for_fbig!(f32);
104impl_from_float_for_fbig!(f64);
105
106impl<R: Round, const B: Word> FBig<R, B> {
107    /// Convert the float number to base 10 (with decimal exponents) rounding to even
108    /// and tying away from zero.
109    ///
110    /// It's equivalent to `self.with_rounding::<HalfAway>().with_base::<10>()`.
111    /// The output is directly of type [DBig][crate::DBig].
112    ///
113    /// See [with_base()][Self::with_base] for the precision behavior.
114    ///
115    /// # Examples
116    ///
117    /// ```
118    /// # use core::str::FromStr;
119    /// # use dashu_base::ParseError;
120    /// # use dashu_float::{FBig, DBig};
121    /// use dashu_base::Approximation::*;
122    /// use dashu_float::round::Rounding::*;
123    ///
124    /// type Real = FBig;
125    ///
126    /// assert_eq!(
127    ///     Real::from_str("0x1234")?.to_decimal(),
128    ///     Exact(DBig::from_str("4660")?)
129    /// );
130    /// assert_eq!(
131    ///     Real::from_str("0x12.34")?.to_decimal(),
132    ///     Inexact(DBig::from_str("18.20")?, NoOp)
133    /// );
134    /// assert_eq!(
135    ///     Real::from_str("0x1.234p-4")?.to_decimal(),
136    ///     Inexact(DBig::from_str("0.07111")?, AddOne)
137    /// );
138    /// # Ok::<(), ParseError>(())
139    /// ```
140    ///
141    /// # Panics
142    ///
143    /// Panics if the associated context has unlimited precision and the conversion
144    /// cannot be performed losslessly.
145    #[inline]
146    pub fn to_decimal(&self) -> Rounded<FBig<HalfAway, 10>> {
147        self.clone().with_rounding().with_base::<10>()
148    }
149
150    /// Convert the float number to base 2 (with binary exponents) rounding towards zero.
151    ///
152    /// It's equivalent to `self.with_rounding::<Zero>().with_base::<2>()`.
153    ///
154    /// See [with_base()][Self::with_base] for the precision and rounding behavior.
155    ///
156    /// # Examples
157    ///
158    /// ```
159    /// # use core::str::FromStr;
160    /// # use dashu_base::ParseError;
161    /// # use dashu_float::{FBig, DBig};
162    /// use dashu_base::Approximation::*;
163    /// use dashu_float::round::{mode::HalfAway, Rounding::*};
164    ///
165    /// type Real = FBig;
166    ///
167    /// assert_eq!(
168    ///     DBig::from_str("1234")?.to_binary(),
169    ///     Exact(Real::from_str("0x4d2")?)
170    /// );
171    /// assert_eq!(
172    ///     DBig::from_str("12.34")?.to_binary(),
173    ///     Inexact(Real::from_str("0xc.57")?, NoOp)
174    /// );
175    /// assert_eq!(
176    ///     DBig::from_str("1.234e-1")?.to_binary(),
177    ///     Inexact(Real::from_str("0x1.f97p-4")?, NoOp)
178    /// );
179    /// # Ok::<(), ParseError>(())
180    /// ```
181    ///
182    /// # Panics
183    ///
184    /// Panics if the associated context has unlimited precision and the conversion
185    /// cannot be performed losslessly.
186    #[inline]
187    pub fn to_binary(&self) -> Rounded<FBig<Zero, 2>> {
188        self.clone().with_rounding().with_base::<2>()
189    }
190
191    /// Explicitly change the precision of the float number.
192    ///
193    /// If the given precision is less than the current value in the context,
194    /// it will be rounded with the rounding mode specified by the generic parameter.
195    ///
196    /// # Examples
197    ///
198    /// ```rust
199    /// # use core::str::FromStr;
200    /// # use dashu_base::ParseError;
201    /// # use dashu_float::{FBig, DBig};
202    /// use dashu_base::Approximation::*;
203    /// use dashu_float::round::{mode::HalfAway, Rounding::*};
204    ///
205    /// let a = DBig::from_str("2.345")?;
206    /// assert_eq!(a.precision(), 4);
207    /// assert_eq!(
208    ///     a.clone().with_precision(3),
209    ///     Inexact(DBig::from_str("2.35")?, AddOne)
210    /// );
211    /// assert_eq!(
212    ///     a.clone().with_precision(5),
213    ///     Exact(DBig::from_str("2.345")?)
214    /// );
215    /// # Ok::<(), ParseError>(())
216    /// ```
217    #[inline]
218    pub fn with_precision(self, precision: usize) -> Rounded<Self> {
219        let new_context = Context::new(precision);
220
221        // shrink if necessary
222        let repr = if self.context.precision > precision {
223            // it also handles unlimited precision
224            new_context.repr_round(self.repr)
225        } else {
226            Exact(self.repr)
227        };
228
229        repr.map(|v| Self::new(v, new_context))
230    }
231
232    /// Explicitly change the rounding mode of the number.
233    ///
234    /// This operation doesn't modify the underlying representation, it only changes
235    /// the rounding mode in the context.
236    ///
237    /// # Examples
238    ///
239    /// ```rust
240    /// # use core::str::FromStr;
241    /// # use dashu_base::ParseError;
242    /// # use dashu_float::{FBig, DBig};
243    /// use dashu_base::Approximation::*;
244    /// use dashu_float::round::{mode::{HalfAway, Zero}, Rounding::*};
245    ///
246    /// type DBigHalfAway = DBig;
247    /// type DBigZero = FBig::<Zero, 10>;
248    ///
249    /// let a = DBigHalfAway::from_str("2.345")?;
250    /// let b = DBigZero::from_str("2.345")?;
251    /// assert_eq!(a.with_rounding::<Zero>(), b);
252    /// # Ok::<(), ParseError>(())
253    /// ```
254    #[inline]
255    pub fn with_rounding<NewR: Round>(self) -> FBig<NewR, B> {
256        FBig {
257            repr: self.repr,
258            context: Context::new(self.context.precision),
259        }
260    }
261
262    /// Explicitly change the base of the float number.
263    ///
264    /// This function internally calls [with_base_and_precision][Self::with_base_and_precision].
265    /// The precision of the result number will be calculated in such a way that the new
266    /// limit of the significand is less than or equal to before. That is, the new precision
267    /// will be the max integer such that
268    ///
269    /// `NewB ^ new_precision <= B ^ old_precision`
270    ///
271    /// If any rounding happens during the conversion, it follows the rounding mode specified
272    /// by the generic parameter.
273    ///
274    /// # Examples
275    ///
276    /// ```rust
277    /// # use core::str::FromStr;
278    /// # use dashu_base::ParseError;
279    /// # use dashu_float::{FBig, DBig};
280    /// use dashu_base::Approximation::*;
281    /// use dashu_float::round::{mode::Zero, Rounding::*};
282    ///
283    /// type FBin = FBig;
284    /// type FDec = FBig<Zero, 10>;
285    /// type FHex = FBig<Zero, 16>;
286    ///
287    /// let a = FBin::from_str("0x1.234")?; // 0x1234 * 2^-12
288    /// assert_eq!(
289    ///     a.clone().with_base::<10>(),
290    ///     // 1.1376953125 rounded towards zero
291    ///     Inexact(FDec::from_str("1.137")?, NoOp)
292    /// );
293    /// assert_eq!(
294    ///     a.clone().with_base::<16>(),
295    ///     // conversion is exact when the new base is a power of the old base
296    ///     Exact(FHex::from_str("1.234")?)
297    /// );
298    /// # Ok::<(), ParseError>(())
299    /// ```
300    ///
301    /// # Panics
302    ///
303    /// Panics if the associated context has unlimited precision and the conversion
304    /// cannot be performed losslessly.
305    #[inline]
306    #[allow(non_upper_case_globals)]
307    pub fn with_base<const NewB: Word>(self) -> Rounded<FBig<R, NewB>> {
308        // if self.context.precision is zero, then precision is also zero
309        let precision =
310            Repr::<B>::BASE.pow(self.context.precision).log2_bounds().0 / NewB.log2_bounds().1;
311        self.with_base_and_precision(precision as usize)
312    }
313
314    /// Explicitly change the base of the float number with given precision (under the new base).
315    ///
316    /// Infinities are mapped to infinities inexactly, the error will be [NoOp][Rounding::NoOp].
317    ///
318    /// Conversion for float numbers with unlimited precision is only allowed in following cases:
319    /// - The number is infinite
320    /// - The new base NewB is a power of B
321    /// - B is a power of the new base NewB
322    ///
323    /// # Examples
324    ///
325    /// ```rust
326    /// # use core::str::FromStr;
327    /// # use dashu_base::ParseError;
328    /// # use dashu_float::{FBig, DBig};
329    /// use dashu_base::Approximation::*;
330    /// use dashu_float::round::{mode::Zero, Rounding::*};
331    ///
332    /// type FBin = FBig;
333    /// type FDec = FBig<Zero, 10>;
334    /// type FHex = FBig<Zero, 16>;
335    ///
336    /// let a = FBin::from_str("0x1.234")?; // 0x1234 * 2^-12
337    /// assert_eq!(
338    ///     a.clone().with_base_and_precision::<10>(8),
339    ///     // 1.1376953125 rounded towards zero
340    ///     Inexact(FDec::from_str("1.1376953")?, NoOp)
341    /// );
342    /// assert_eq!(
343    ///     a.clone().with_base_and_precision::<16>(8),
344    ///     // conversion can be exact when the new base is a power of the old base
345    ///     Exact(FHex::from_str("1.234")?)
346    /// );
347    /// assert_eq!(
348    ///     a.clone().with_base_and_precision::<16>(2),
349    ///     // but the conversion is still inexact if the target precision is smaller
350    ///     Inexact(FHex::from_str("1.2")?, NoOp)
351    /// );
352    /// # Ok::<(), ParseError>(())
353    /// ```
354    ///
355    /// # Panics
356    ///
357    /// Panics if the associated context has unlimited precision and the conversion
358    /// cannot be performed losslessly.
359    #[allow(non_upper_case_globals)]
360    #[inline]
361    pub fn with_base_and_precision<const NewB: Word>(
362        self,
363        precision: usize,
364    ) -> Rounded<FBig<R, NewB>> {
365        let context = Context::<R>::new(precision);
366        context
367            .convert_base(self.repr, None)
368            .map(|repr| FBig::new(repr, context))
369    }
370
371    /// Convert the float number to integer with the given rounding mode.
372    ///
373    /// # Warning
374    ///
375    /// If the float number has a very large exponent, it will be evaluated and result
376    /// in allocating an huge integer and it might eat up all your memory.
377    ///
378    /// To get a rough idea of how big the number is, it's recommended to use [EstimatedLog2].
379    ///
380    /// # Examples
381    ///
382    /// ```
383    /// # use core::str::FromStr;
384    /// # use dashu_base::ParseError;
385    /// # use dashu_float::{FBig, DBig};
386    /// use dashu_base::Approximation::*;
387    /// use dashu_float::round::Rounding::*;
388    ///
389    /// assert_eq!(
390    ///     DBig::from_str("1234")?.to_int(),
391    ///     Exact(1234.into())
392    /// );
393    /// assert_eq!(
394    ///     DBig::from_str("1.234e6")?.to_int(),
395    ///     Exact(1234000.into())
396    /// );
397    /// assert_eq!(
398    ///     DBig::from_str("1.234")?.to_int(),
399    ///     Inexact(1.into(), NoOp)
400    /// );
401    /// # Ok::<(), ParseError>(())
402    /// ```
403    ///
404    /// # Panics
405    ///
406    /// Panics if the number is infinte
407    pub fn to_int(&self) -> Rounded<IBig> {
408        assert_finite(&self.repr);
409
410        // shortcut when the number is already an integer
411        if self.repr.exponent >= 0 {
412            return Exact(shl_digits::<B>(&self.repr.significand, self.repr.exponent as usize));
413        }
414
415        let (hi, lo, precision) = self.split_at_point_internal();
416        let adjust = R::round_fract::<B>(&hi, lo, precision);
417        Inexact(hi + adjust, adjust)
418    }
419
420    /// Convert the float number to [f32] with the rounding mode associated with the type.
421    ///
422    /// Note that the conversion is inexact even if the number is infinite.
423    ///
424    /// # Examples
425    ///
426    /// ```
427    /// # use core::str::FromStr;
428    /// # use dashu_base::ParseError;
429    /// # use dashu_float::DBig;
430    /// assert_eq!(DBig::from_str("1.234")?.to_f32().value(), 1.234);
431    /// assert_eq!(DBig::INFINITY.to_f32().value(), f32::INFINITY);
432    /// # Ok::<(), ParseError>(())
433    /// ```
434    #[inline]
435    pub fn to_f32(&self) -> Rounded<f32> {
436        Context::<R>::convert_to_f32(self.repr.clone())
437    }
438
439    /// Convert the float number to [f64] with the rounding mode associated with the type.
440    ///
441    /// Note that the conversion is inexact even if the number is infinite.
442    ///
443    /// # Examples
444    ///
445    /// ```
446    /// # use core::str::FromStr;
447    /// # use dashu_base::ParseError;
448    /// # use dashu_float::DBig;
449    /// assert_eq!(DBig::from_str("1.234")?.to_f64().value(), 1.234);
450    /// assert_eq!(DBig::INFINITY.to_f64().value(), f64::INFINITY);
451    /// # Ok::<(), ParseError>(())
452    /// ```
453    #[inline]
454    pub fn to_f64(&self) -> Rounded<f64> {
455        Context::<R>::convert_to_f64(self.repr.clone())
456    }
457}
458
459/// `isize` exponent arithmetic overflowed during base conversion: the value's magnitude
460/// falls outside the representable exponent range, so the result is ±infinity (`large`) or
461/// ±0 (`!large`). Mirrors the convention `convert_base` already uses in its division path,
462/// keeping the conversion overflow-safe (no panic) at the value level.
463#[allow(non_upper_case_globals)]
464fn converted_overflow_repr<const NewB: Word>(large: bool, sign: Sign) -> Rounded<Repr<NewB>> {
465    Inexact(
466        if large {
467            Repr::<NewB>::infinity_with_sign(sign)
468        } else {
469            Repr::<NewB>::zero_with_sign(sign)
470        },
471        Rounding::NoOp,
472    )
473}
474
475/// Number of significant bits a binary float format keeps for a value whose most-significant bit
476/// sits at position `msb`: `max_bits` across the normal range, but fewer for subnormals, whose
477/// spacing is fixed at `2^subnormal_exp` (e.g. `2^-1074` for f64, `2^-149` for f32). Rounding the
478/// source straight to this width lets the bit-encoding step avoid a second rounding, which would
479/// otherwise double-round subnormals.
480fn significand_bits(v: &Repr<2>, max_bits: usize, subnormal_exp: isize) -> usize {
481    if v.significand.is_zero() {
482        return max_bits;
483    }
484    let msb = v.exponent + v.digits() as isize - 1;
485    (msb - subnormal_exp + 1).clamp(1, max_bits as isize) as usize
486}
487
488/// Convert `repr` to base 2 and truncate to `width` significant bits, forcing the lowest kept bit
489/// to 1 whenever the tail is nonzero (round-to-odd). Rounding this down to any width up to
490/// `width - 2` reproduces the correctly-rounded value for every rounding mode, so the two-step
491/// "convert, then round to the final width" cannot double-round. `width` is fixed and generous, so
492/// the base-conversion logarithm stays accurate even when the final width is tiny (deep subnormals).
493#[allow(non_upper_case_globals)]
494fn convert_base_odd<const B: Word>(repr: Repr<B>, width: usize) -> Repr<2> {
495    match Context::<Zero>::new(width).convert_base::<B, 2>(repr, None) {
496        Exact(v) => v,
497        Inexact(v, _) if v.significand.is_zero() => v,
498        Inexact(v, _) => {
499            let shift = width - v.digits();
500            let (sign, mut mag) = v.significand.into_parts();
501            mag <<= shift;
502            mag.set_bit(0);
503            Repr::new(IBig::from_parts(sign, mag), v.exponent - shift as isize)
504        }
505    }
506}
507
508impl<R: Round> Context<R> {
509    // Convert `repr` (base B) to the nearest f64 under this context's rounding mode. A generous
510    // round-to-odd base conversion is rounded once to the target's precision at its own magnitude
511    // (fewer than 53 bits for subnormals), so `into_f64_internal` re-rounds nothing — which would
512    // otherwise double-round subnormals. Handles a source significand of any size.
513    fn convert_to_f64<const B: Word>(repr: Repr<B>) -> Rounded<f64> {
514        if repr.is_infinite() {
515            return Inexact(repr.sign() * f64::INFINITY, Rounding::NoOp);
516        }
517        let odd = convert_base_odd::<B>(repr, 60);
518        let bits = significand_bits(&odd, 53, -1074);
519        Context::<R>::new(bits)
520            .repr_round(odd)
521            .and_then(|v| v.into_f64_internal())
522    }
523
524    // [convert_to_f64] for f32.
525    fn convert_to_f32<const B: Word>(repr: Repr<B>) -> Rounded<f32> {
526        if repr.is_infinite() {
527            return Inexact(repr.sign() * f32::INFINITY, Rounding::NoOp);
528        }
529        let odd = convert_base_odd::<B>(repr, 32);
530        let bits = significand_bits(&odd, 24, -149);
531        Context::<R>::new(bits)
532            .repr_round(odd)
533            .and_then(|v| v.into_f32_internal())
534    }
535
536    // Convert the [Repr] from base B to base NewB, with the precision under the target base from this context.
537    #[allow(non_upper_case_globals)]
538    fn convert_base<const B: Word, const NewB: Word>(
539        &self,
540        repr: Repr<B>,
541        mut cache: Option<&mut ConstCache>,
542    ) -> Rounded<Repr<NewB>> {
543        // shortcut if NewB is the same as B
544        if NewB == B {
545            return Exact(Repr {
546                significand: repr.significand,
547                exponent: repr.exponent,
548            });
549        }
550
551        // shortcut for infinities, no rounding happens but the result is inexact
552        if repr.is_infinite() {
553            return Inexact(
554                Repr {
555                    significand: repr.significand,
556                    exponent: repr.exponent,
557                },
558                Rounding::NoOp,
559            );
560        }
561
562        if NewB > B {
563            // shortcut if NewB is a power of B
564            let n = ilog_exact(NewB, B);
565            if n > 1 {
566                let (exp, rem) = repr.exponent.div_rem_euclid(n as isize);
567                let signif = repr.significand * B.pow(rem as u32);
568                let repr = Repr::new(signif, exp);
569                return self.repr_round(repr);
570            }
571        } else {
572            // shortcut if B is a power of NewB
573            let n = ilog_exact(B, NewB);
574            if n > 1 {
575                let exp = match repr.exponent.checked_mul(n as isize) {
576                    Some(e) => e,
577                    None => return converted_overflow_repr::<NewB>(repr.exponent > 0, repr.sign()),
578                };
579                return Exact(Repr::new(repr.significand, exp));
580            }
581        }
582
583        // Shortcut: when B and NewB share common factors, factor out the common part.
584        // B = NewB^a * r where gcd(r, NewB) = 1, so B^exp = NewB^(a*exp) * r^exp.
585        // For positive exponents the result is always exact (integer multiplication).
586        // For negative exponents, exact only when r^|exp| divides the significand.
587        let (a, r) = factor_base(B, NewB);
588        if a > 0 && r > 1 {
589            if repr.exponent >= 0 {
590                let sign = repr.sign();
591                let r_exp = UBig::from_word(r).pow(repr.exponent as usize);
592                let significand = repr.significand * r_exp;
593                let exp = match (a as isize).checked_mul(repr.exponent) {
594                    Some(e) => e,
595                    None => return converted_overflow_repr::<NewB>(true, sign),
596                };
597                let new_repr = Repr::<NewB>::new(significand, exp);
598                return self.repr_round(new_repr);
599            } else {
600                let r_exp: IBig = UBig::from_word(r).pow((-repr.exponent) as usize).into();
601                if repr.significand.is_multiple_of(&r_exp) {
602                    let exp = match (a as isize).checked_mul(repr.exponent) {
603                        Some(e) => e,
604                        None => return converted_overflow_repr::<NewB>(false, repr.sign()),
605                    };
606                    let new_repr = Repr::<NewB>::new(repr.significand / r_exp, exp);
607                    return self.repr_round(new_repr);
608                }
609            }
610        }
611
612        // When NewB is a multiple of B: compute significand * B^exp directly
613        // as an integer, then express in base NewB.
614        if NewB % B == 0 && repr.exponent >= 0 {
615            let signif = repr.significand * Repr::<B>::BASE.pow(repr.exponent as usize);
616            let new_repr = Repr::<NewB>::new(signif, 0);
617            return self.repr_round(new_repr);
618        }
619
620        // if the base cannot be converted losslessly, the precision must be set
621        if self.precision == 0 {
622            panic_unlimited_precision();
623        }
624
625        // choose a exponent threshold such that number with exponent smaller than this value
626        // will be converted by directly evaluating the power. The threshold here is chosen such
627        // that the power under base 10 will fit in a double word.
628        const THRESHOLD_SMALL_EXP: isize = (Word::BITS as f32 * 0.60206) as isize; // word bits * 2 / log2(10)
629        if repr.exponent.abs() <= THRESHOLD_SMALL_EXP {
630            // if the exponent is small enough, directly evaluate the exponent
631            if repr.exponent >= 0 {
632                let signif = repr.significand * Repr::<B>::BASE.pow(repr.exponent as usize);
633                Exact(Repr::new(signif, 0))
634            } else {
635                let den: Repr<NewB> =
636                    Repr::new(Repr::<B>::BASE.pow(-repr.exponent as usize).into(), 0);
637                // repr_div requires the dividend to be no wider than `precision + divisor`, so
638                // pre-shrink the significand the same way Context::div does — the caller, not
639                // the kernel, is responsible for bounding the dividend. Rounding it to
640                // `den.digits() + precision` preserves enough information for the division to
641                // be correctly rounded at `precision`.
642                let num: Repr<NewB> = Repr::new(repr.significand, 0);
643                let num =
644                    if !num.is_pos_zero() && num.digits_ub() > den.digits_lb() + self.precision {
645                        Self::new(den.digits() + self.precision)
646                            .repr_round_ref(&num)
647                            .value()
648                    } else {
649                        num
650                    };
651                match self.repr_div(num, den) {
652                    Ok(v) => v.map(|r: Repr<NewB>| Repr {
653                        significand: r.significand,
654                        exponent: r.exponent,
655                    }),
656                    Err(FpError::Overflow(sign)) => {
657                        Inexact(Repr::<NewB>::infinity_with_sign(sign), Rounding::NoOp)
658                    }
659                    Err(FpError::Underflow(sign)) => {
660                        Inexact(Repr::<NewB>::zero_with_sign(sign), Rounding::NoOp)
661                    }
662                    Err(_) => unreachable!(),
663                }
664            }
665        } else {
666            // if the exponent is large, then we first estimate the result exponent as floor(exponent * log(B) / log(NewB)),
667            // then the fractional part is multiplied with the original significand
668            let work_context = Context::<R>::new(2 * self.precision); // double the precision to get the precise logarithm
669                                                                      // ln(old base) and ln(new base) — near-correct is sufficient for the exponent estimate,
670                                                                      // and using the near-correct `ln_compute`/`ln_base` (R: Round) keeps base conversion
671                                                                      // off the `ErrorBounds` bound. Both are computed in base NewB so the euclidean division
672                                                                      // has matching bases.
673            let new_exp = repr.exponent
674                * work_context
675                    .ln_compute::<NewB>(
676                        &Repr::new(Repr::<B>::BASE.into(), 0),
677                        work_context.precision,
678                        false,
679                        reborrow_cache(&mut cache),
680                    )
681                    .0;
682            let (exponent, rem) =
683                new_exp.div_rem_euclid(work_context.ln_base::<NewB>(reborrow_cache(&mut cache)));
684            let exponent_sign = exponent.sign();
685            let exponent: isize = match exponent.try_into() {
686                Ok(v) => v,
687                Err(_) => {
688                    return converted_overflow_repr::<NewB>(
689                        exponent_sign == Sign::Positive,
690                        repr.sign(),
691                    );
692                }
693            };
694            // exp(fractional exponent) — near-correct is sufficient (it scales the significand),
695            // so use `exp_compute` (R: Round) and stay off the `ErrorBounds` bound.
696            let n = 1usize << (work_context.precision.bit_len() / 2);
697            let exp_rem = work_context
698                .exp_compute::<NewB>(
699                    &rem.repr,
700                    work_context.precision,
701                    false,
702                    n,
703                    reborrow_cache(&mut cache),
704                )
705                .0;
706            let significand = repr.significand * exp_rem.repr.significand;
707            let repr = Repr::new(significand, exponent + exp_rem.repr.exponent);
708            self.repr_round(repr)
709        }
710    }
711}
712
713impl<const B: Word> Repr<B> {
714    // this method requires that the representation is already rounded to 24 binary bits
715    fn into_f32_internal(self) -> Rounded<f32> {
716        assert!(B == 2);
717        debug_assert!(self.is_finite());
718        debug_assert!(self.significand.bit_len() <= 24);
719
720        let sign = self.sign();
721        if self.is_neg_zero() {
722            // encode() would drop the sign of -0; preserve it exactly
723            return Exact(sign * 0f32);
724        }
725        let man24: i32 = self.significand.try_into().unwrap();
726        if self.exponent >= 128 {
727            // max f32 = 2^128 * (1 - 2^-24)
728            match sign {
729                Sign::Positive => Inexact(f32::INFINITY, Rounding::AddOne),
730                Sign::Negative => Inexact(f32::NEG_INFINITY, Rounding::SubOne),
731            }
732        } else if self.exponent < -149 - 24 {
733            // min f32 = 2^-149
734            Inexact(sign * 0f32, Rounding::NoOp)
735        } else {
736            match f32::encode(man24, self.exponent as i16) {
737                Exact(v) => Exact(v),
738                // this branch only happens when the result underflows
739                Inexact(v, _) => Inexact(v, Rounding::NoOp),
740            }
741        }
742    }
743
744    /// Convert the float number representation to a [f32] with the default IEEE 754 rounding mode.
745    ///
746    /// The default IEEE 754 rounding mode is [HalfEven] (rounding to nearest, ties to even). To convert
747    /// the float number with a specific rounding mode, please use [FBig::to_f32].
748    ///
749    /// # Examples
750    ///
751    /// ```
752    /// # use dashu_base::Approximation::*;
753    /// # use dashu_float::{Repr, round::Rounding::*};
754    /// assert_eq!(Repr::<2>::one().to_f32(), Exact(1.0));
755    /// assert_eq!(Repr::<10>::infinity().to_f32(), Inexact(f32::INFINITY, NoOp));
756    /// ```
757    #[inline]
758    pub fn to_f32(&self) -> Rounded<f32> {
759        Context::<HalfEven>::convert_to_f32(self.clone())
760    }
761
762    // this method requires that the representation is already rounded to 53 binary bits
763    fn into_f64_internal(self) -> Rounded<f64> {
764        assert!(B == 2);
765        debug_assert!(self.is_finite());
766        debug_assert!(self.significand.bit_len() <= 53);
767
768        let sign = self.sign();
769        if self.is_neg_zero() {
770            // encode() would drop the sign of -0; preserve it exactly
771            return Exact(sign * 0f64);
772        }
773        let man53: i64 = self.significand.try_into().unwrap();
774        if self.exponent >= 1024 {
775            // max f64 = 2^1024 × (1 − 2^−53)
776            match sign {
777                Sign::Positive => Inexact(f64::INFINITY, Rounding::AddOne),
778                Sign::Negative => Inexact(f64::NEG_INFINITY, Rounding::SubOne),
779            }
780        } else if self.exponent < -1074 - 53 {
781            // min f64 = 2^-1074
782            Inexact(sign * 0f64, Rounding::NoOp)
783        } else {
784            match f64::encode(man53, self.exponent as i16) {
785                Exact(v) => Exact(v),
786                // this branch only happens when the result underflows
787                Inexact(v, _) => Inexact(v, Rounding::NoOp),
788            }
789        }
790    }
791
792    /// Convert the float number representation to a [f64] with the default IEEE 754 rounding mode.
793    ///
794    /// The default IEEE 754 rounding mode is [HalfEven] (rounding to nearest, ties to even). To convert
795    /// the float number with a specific rounding mode, please use [FBig::to_f64].
796    ///
797    /// # Examples
798    ///
799    /// ```
800    /// # use dashu_base::Approximation::*;
801    /// # use dashu_float::{Repr, round::Rounding::*};
802    /// assert_eq!(Repr::<2>::one().to_f64(), Exact(1.0));
803    /// assert_eq!(Repr::<10>::infinity().to_f64(), Inexact(f64::INFINITY, NoOp));
804    /// ```
805    #[inline]
806    pub fn to_f64(&self) -> Rounded<f64> {
807        Context::<HalfEven>::convert_to_f64(self.clone())
808    }
809
810    /// Convert the float number representation to a [IBig].
811    ///
812    /// The fractional part is always rounded to zero. To convert with other rounding modes,
813    /// please use [FBig::to_int()].
814    ///
815    /// # Warning
816    ///
817    /// If the float number has a very large exponent, it will be evaluated and result
818    /// in allocating an huge integer and it might eat up all your memory.
819    ///
820    /// To get a rough idea of how big the number is, it's recommended to use [EstimatedLog2].
821    ///
822    /// # Examples
823    ///
824    /// ```
825    /// # use dashu_base::Approximation::*;
826    /// # use dashu_int::IBig;
827    /// # use dashu_float::{Repr, round::Rounding::*};
828    /// assert_eq!(Repr::<2>::neg_one().to_int(), Exact(IBig::NEG_ONE));
829    /// ```
830    ///
831    /// # Panics
832    ///
833    /// Panics if the number is infinte.
834    pub fn to_int(&self) -> Rounded<IBig> {
835        assert_finite(self);
836
837        if self.exponent >= 0 {
838            // the number is already an integer
839            Exact(shl_digits::<B>(&self.significand, self.exponent as usize))
840        } else if self.smaller_than_one() {
841            // the number is definitely smaller than
842            Inexact(IBig::ZERO, Rounding::NoOp)
843        } else {
844            let int = shr_digits::<B>(&self.significand, (-self.exponent) as usize);
845            Inexact(int, Rounding::NoOp)
846        }
847    }
848}
849
850impl<const B: Word> From<UBig> for Repr<B> {
851    #[inline]
852    fn from(n: UBig) -> Self {
853        Self::new(n.into(), 0)
854    }
855}
856impl<R: Round, const B: Word> From<UBig> for FBig<R, B> {
857    #[inline]
858    fn from(n: UBig) -> Self {
859        Self::from_parts(n.into(), 0)
860    }
861}
862
863impl<const B: Word> From<IBig> for Repr<B> {
864    #[inline]
865    fn from(n: IBig) -> Self {
866        Self::new(n, 0)
867    }
868}
869impl<R: Round, const B: Word> From<IBig> for FBig<R, B> {
870    #[inline]
871    fn from(n: IBig) -> Self {
872        Self::from_parts(n, 0)
873    }
874}
875
876impl<R: Round, const B: Word> TryFrom<FBig<R, B>> for IBig {
877    type Error = ConversionError;
878
879    #[inline]
880    fn try_from(value: FBig<R, B>) -> Result<Self, Self::Error> {
881        if value.repr.is_infinite() {
882            Err(ConversionError::OutOfBounds)
883        } else if value.repr.significand.is_zero() {
884            // A zero significand is integer zero regardless of exponent. This also
885            // accepts IEEE-754 signed zero, whose sign is carried by a -1 exponent
886            // sentinel (not the significand); it is treated as plain 0. The zero
887            // must be handled here rather than in the `else` branch below, which
888            // shifts by `exponent as usize` and would underflow on the -1 sentinel.
889            Ok(value.repr.significand)
890        } else if value.repr.exponent < 0 {
891            Err(ConversionError::LossOfPrecision)
892        } else {
893            let mut int = value.repr.significand;
894            shl_digits_in_place::<B>(&mut int, value.repr.exponent as usize);
895            Ok(int)
896        }
897    }
898}
899
900impl<R: Round, const B: Word> TryFrom<FBig<R, B>> for UBig {
901    type Error = ConversionError;
902
903    #[inline]
904    fn try_from(value: FBig<R, B>) -> Result<Self, Self::Error> {
905        let int: IBig = value.try_into()?;
906        int.try_into()
907    }
908}
909
910macro_rules! fbig_unsigned_conversions {
911    ($($t:ty)*) => {$(
912        impl<const B: Word> From<$t> for Repr<B> {
913            #[inline]
914            fn from(value: $t) -> Repr<B> {
915                UBig::from(value).into()
916            }
917        }
918        impl<R: Round, const B: Word> From<$t> for FBig<R, B> {
919            #[inline]
920            fn from(value: $t) -> FBig<R, B> {
921                UBig::from(value).into()
922            }
923        }
924
925        impl<const B: Word> TryFrom<Repr<B>> for $t {
926            type Error = ConversionError;
927
928            fn try_from(value: Repr<B>) -> Result<Self, Self::Error> {
929                if value.sign() == Sign::Negative || value.is_infinite() {
930                    Err(ConversionError::OutOfBounds)
931                } else {
932                    let (log2_lb, _) = value.log2_bounds();
933                    if log2_lb >= <$t>::BITS as f32 {
934                        Err(ConversionError::OutOfBounds)
935                    } else if value.exponent < 0 {
936                        Err(ConversionError::LossOfPrecision)
937                    } else {
938                        shl_digits::<B>(&value.significand, value.exponent as usize).try_into()
939                    }
940                }
941            }
942        }
943        impl<R: Round, const B: Word> TryFrom<FBig<R, B>> for $t {
944            type Error = ConversionError;
945
946            #[inline]
947            fn try_from(value: FBig<R, B>) -> Result<Self, Self::Error> {
948                value.repr.try_into()
949            }
950        }
951    )*};
952}
953fbig_unsigned_conversions!(u8 u16 u32 u64 u128 usize);
954
955macro_rules! fbig_signed_conversions {
956    ($($t:ty)*) => {$(
957        impl<R: Round, const B: Word> From<$t> for FBig<R, B> {
958            #[inline]
959            fn from(value: $t) -> FBig<R, B> {
960                IBig::from(value).into()
961            }
962        }
963
964        impl<R: Round, const B: Word> TryFrom<FBig<R, B>> for $t {
965            type Error = ConversionError;
966
967            fn try_from(value: FBig<R, B>) -> Result<Self, Self::Error> {
968                if value.repr.is_infinite() {
969                    Err(ConversionError::OutOfBounds)
970                } else {
971                    let (log2_lb, _) = value.repr.log2_bounds();
972                    if log2_lb >= <$t>::BITS as f32 {
973                        Err(ConversionError::OutOfBounds)
974                    } else if value.repr.exponent < 0 {
975                        Err(ConversionError::LossOfPrecision)
976                    } else {
977                        shl_digits::<B>(&value.repr.significand, value.repr.exponent as usize).try_into()
978                    }
979                }
980            }
981        }
982    )*};
983}
984fbig_signed_conversions!(i8 i16 i32 i64 i128 isize);
985
986macro_rules! impl_from_fbig_for_float {
987    ($t:ty, $method:ident) => {
988        impl TryFrom<Repr<2>> for $t {
989            type Error = ConversionError;
990
991            #[inline]
992            fn try_from(value: Repr<2>) -> Result<Self, Self::Error> {
993                if value.is_infinite() {
994                    Err(ConversionError::LossOfPrecision)
995                } else {
996                    match value.$method() {
997                        Exact(v) => Ok(v),
998                        Inexact(v, _) => {
999                            if v.is_infinite() {
1000                                Err(ConversionError::OutOfBounds)
1001                            } else {
1002                                Err(ConversionError::LossOfPrecision)
1003                            }
1004                        }
1005                    }
1006                }
1007            }
1008        }
1009
1010        impl<R: Round> TryFrom<FBig<R, 2>> for $t {
1011            type Error = ConversionError;
1012
1013            #[inline]
1014            fn try_from(value: FBig<R, 2>) -> Result<Self, Self::Error> {
1015                // this method is the same as the one for Repr, but it has to be re-implemented
1016                // because the rounding behavior of to_32/to_64 is different.
1017                if value.repr.is_infinite() {
1018                    Err(ConversionError::LossOfPrecision)
1019                } else {
1020                    match value.$method() {
1021                        Exact(v) => Ok(v),
1022                        Inexact(v, _) => {
1023                            if v.is_infinite() {
1024                                Err(ConversionError::OutOfBounds)
1025                            } else {
1026                                Err(ConversionError::LossOfPrecision)
1027                            }
1028                        }
1029                    }
1030                }
1031            }
1032        }
1033    };
1034}
1035impl_from_fbig_for_float!(f32, to_f32);
1036impl_from_fbig_for_float!(f64, to_f64);
1037
1038#[cfg(test)]
1039mod tests {
1040    use super::*;
1041    use crate::repr::Repr;
1042
1043    #[test]
1044    fn ibig_try_from_accepts_signed_zero() {
1045        // IEEE-754 signed zero (sign encoded in a -1 exponent sentinel) is plain 0.
1046        let neg_zero = FBig::<HalfAway, 10>::new(Repr::neg_zero(), Context::new(8));
1047        assert_eq!(IBig::try_from(neg_zero), Ok(IBig::from(0)));
1048
1049        // positive zero already worked, and still does
1050        let pos_zero = FBig::<HalfAway, 10>::new(Repr::zero(), Context::new(8));
1051        assert_eq!(IBig::try_from(pos_zero), Ok(IBig::from(0)));
1052
1053        // UBig delegates to the IBig impl, so it accepts signed zero too
1054        let neg_zero = FBig::<HalfAway, 2>::new(Repr::neg_zero(), Context::new(8));
1055        assert_eq!(UBig::try_from(neg_zero), Ok(UBig::from(0u8)));
1056
1057        // a genuine fractional value must still be rejected
1058        let frac = FBig::<HalfAway, 10>::new(Repr::new(IBig::from(1), -1), Context::new(8));
1059        assert_eq!(IBig::try_from(frac), Err(ConversionError::LossOfPrecision));
1060
1061        // a normal integer round-trips exactly
1062        let int_val = FBig::<HalfAway, 10>::new(Repr::new(IBig::from(42), 0), Context::new(8));
1063        assert_eq!(IBig::try_from(int_val), Ok(IBig::from(42)));
1064    }
1065
1066    #[test]
1067    fn with_base_high_precision_no_overflow() {
1068        // Regression for issue #95: converting a high-precision base-2 float to base
1069        // 10 panicked on 32-bit targets ("arithmetic operations with the infinity are
1070        // not allowed!"). The base conversion evaluates exp(r) as `sum^(B^n)` through
1071        // `powi` with a huge exponent (B^n) on a base (sum) very close to 1; `powi`'s
1072        // overflow guard estimated log2(base) with the catastrophically-canceling
1073        // `log2_est`, and the ~1e-4 of f32 noise scaled by the exponent crossed the
1074        // (much smaller on 32-bit) isize threshold, yielding a spurious ±inf that then
1075        // panicked when shifted. See `powi` in exp.rs for the fix.
1076        use crate::round::mode::Zero;
1077        use core::str::FromStr;
1078
1079        // The reporter's input: -1.1111…0011 in binary (578 significant bits), written
1080        // in the hex form dashu accepts for base-2 floats (`0x1.<hex>…`). The value is
1081        // identical to the raw binary literal.
1082        let num = FBig::<Zero, 2>::from_str(
1083            "-0x1.fffdc8d645194a5a95df4be063472d4406dd096339dd7dc2a8527d208b3da7b9e5c36b4f49a7982cb2ad20a4e7e4c016f858fe8cddea011a6d01fe3823189c4ed4f57a7babc331498",
1084        )
1085        .unwrap();
1086
1087        // at the original 578-bit precision the conversion succeeds …
1088        let a = num
1089            .clone()
1090            .with_precision(578)
1091            .value()
1092            .with_base::<10>()
1093            .value();
1094        assert!(a.repr().is_finite());
1095        // … and so does a slightly higher precision (586), which panicked on 32-bit
1096        // (wasm32 / i686). The result matches the value computed on 64-bit. Compared
1097        // by value (FBig equality ignores context) rather than via string formatting,
1098        // so this works under no_std too.
1099        let b = num.with_precision(586).value().with_base::<10>().value();
1100        assert!(b.repr().is_finite());
1101        let expected = FBig::<Zero, 10>::from_str(
1102            "-1.9999661944503703041843468850635057967553124154072485151176192294480158424234268438137612977886891381228704640656094986435381057574477216648567249609280392009533217665484389886",
1103        )
1104        .unwrap();
1105        assert_eq!(b, expected);
1106    }
1107}