Skip to main content

dashu_float/
repr.rs

1use crate::{
2    error::{assert_finite, FpError},
3    round::{Round, Rounded},
4    utils::{ceil_usize, digit_len, split_digits, split_digits_ref},
5};
6use core::marker::PhantomData;
7use dashu_base::{Approximation::*, EstimatedLog2, Sign};
8pub use dashu_int::Word;
9use dashu_int::{DoubleWord, IBig, UBig};
10
11/// Underlying representation of an arbitrary precision floating number.
12///
13/// The floating point number is represented as `significand * base^exponent`, where the
14/// type of the significand is [IBig], and the type of exponent is [isize]. The representation
15/// is always normalized (nonzero signficand is not divisible by the base, or zero signficand
16/// with zero exponent).
17///
18/// When it's used together with a [Context], its precision will be limited so that
19/// `|significand| < base^precision`. As an intentional exception, the result of an inexact
20/// addition or subtraction may carry one extra guard digit, so `|significand|` can be up to
21/// `base^(precision+1)`; the guard digit is what lets a much-smaller operand be reduced to a
22/// sign-only sticky bit during alignment without mis-rounding.
23///
24/// # Infinity and signed zero
25///
26/// Special values are encoded with a zero significand and a sentinel exponent:
27/// - value zero (`+0`): exponent = 0
28/// - negative zero (`-0`): exponent = -1
29/// - positive infinity (`+inf`): exponent = `isize::MAX`
30/// - negative infinity (`-inf`): exponent = `isize::MIN`
31///
32/// The infinities are only supposed to be consumed as sentinels: only equality test and
33/// comparison are implemented for them, and any arithmetic operation that takes an infinity
34/// as input will lead to panic (at the `FBig` layer) or return an error (at the `Context`
35/// layer). If an operation result is too large or too small, the operation will return an
36/// infinity (as a value) at the `Context` layer, or panic at the `FBig` layer.
37///
38pub struct Repr<const BASE: Word> {
39    /// The significand of the floating point number. If the significand is zero, then the
40    /// number is a special value identified by the exponent (see the struct-level docs):
41    /// `+0`, `-0`, `+inf`, or `-inf`.
42    pub(crate) significand: IBig,
43
44    /// The exponent of the floating point number.
45    pub(crate) exponent: isize,
46}
47
48/// The context containing runtime information for the floating point number and its operations.
49///
50/// The context currently consists of a *precision limit* and a *rounding mode*. All the operation
51/// associated with the context will be precise to the **full precision** (`|error| < 1 ulp`).
52/// The rounding result returned from the functions tells additional error information, see
53/// [the rounding mode module][crate::round::mode] for details.
54///
55/// # Precision
56///
57/// The precision limit determine the number of significant digits in the float number.
58///
59/// For binary operations, the result will have the higher one between the precisions of two
60/// operands.
61///
62/// If the precision is set to 0, then the precision is **unlimited** during operations.
63/// Be cautious to use unlimited precision because it can leads to very huge significands.
64/// Unlimited precision is forbidden for some operations where the result is always inexact.
65///
66/// # Rounding Mode
67///
68/// The rounding mode determines the rounding behavior of the float operations.
69///
70/// See [the rounding mode module][crate::round::mode] for built-in rounding modes.
71/// Users can implement custom rounding mode by implementing the [Round][crate::round::Round]
72/// trait, but this is discouraged since in the future we might restrict the rounding
73/// modes to be chosen from the the built-in modes.
74///
75/// For binary operations, the two oprands must have the same rounding mode.
76///
77#[derive(Clone, Copy)]
78pub struct Context<RoundingMode: Round> {
79    /// The precision of the floating point number.
80    /// If set to zero, then the precision is unlimited.
81    pub(crate) precision: usize,
82    _marker: PhantomData<RoundingMode>,
83}
84
85/// Flip the sign of a special-value exponent: `+0 (0) <-> -0 (-1)`, `+inf (MAX) <-> -inf (MIN)`.
86/// For any other (non-canonical) exponent the plain negation is used, which is safe because such
87/// values have magnitude strictly less than `isize::MAX`.
88#[inline]
89const fn negate_special_exponent(exp: isize) -> isize {
90    match exp {
91        0 => -1,
92        -1 => 0,
93        isize::MAX => isize::MIN,
94        isize::MIN => isize::MAX,
95        other => -other,
96    }
97}
98
99/// Build a `Repr` from a rounded significand, preserving the input sign when rounding
100/// produces zero (`significand * B^exponent` where the significand collapsed to `+0`).
101fn rounded_to_repr<const B: Word>(
102    significand: IBig,
103    exponent: isize,
104    input_negative: bool,
105) -> Repr<B> {
106    if significand.is_zero() && input_negative {
107        Repr::neg_zero()
108    } else {
109        Repr::new(significand, exponent)
110    }
111}
112
113/// Normalize a `(significand, exponent)` pair for base `B`: fold trailing base-`B` zero-digits
114/// from the significand into the exponent, and return the number of significant base-`B` digits.
115///
116/// The return triple is all-`Copy`, so it can be destructured in a `const fn` — unlike a pair
117/// carrying a [`Repr`], whose heap drop can't be const-evaluated. It is the shared core of
118/// [`Repr::new_const`] and [`crate::FBig::from_parts_const`].
119pub(crate) const fn normalize_word_const<const B: Word>(
120    mut significand: DoubleWord,
121    mut exponent: isize,
122) -> (DoubleWord, isize, usize) {
123    if significand == 0 {
124        return (0, exponent, 0);
125    }
126
127    let mut digits = 0;
128    if B.is_power_of_two() {
129        let base_bits = B.trailing_zeros();
130        let shift = significand.trailing_zeros() / base_bits;
131        significand >>= shift * base_bits;
132        exponent += shift as isize;
133        digits =
134            ((DoubleWord::BITS - significand.leading_zeros() + base_bits - 1) / base_bits) as usize;
135    } else {
136        let mut pow: DoubleWord = 1;
137        while significand % (B as DoubleWord) == 0 {
138            significand /= B as DoubleWord;
139            exponent += 1;
140        }
141        while let Some(next) = pow.checked_mul(B as DoubleWord) {
142            digits += 1;
143            if next > significand {
144                break;
145            }
146            pow = next;
147        }
148    }
149    (significand, exponent, digits)
150}
151
152impl<const B: Word> Repr<B> {
153    /// The base of the representation. It's exposed as an [IBig] constant.
154    pub const BASE: UBig = UBig::from_word(B);
155
156    /// Create a [Repr] instance representing value zero
157    #[inline]
158    pub const fn zero() -> Self {
159        Self {
160            significand: IBig::ZERO,
161            exponent: 0,
162        }
163    }
164    /// Create a [Repr] instance representing value one
165    #[inline]
166    pub const fn one() -> Self {
167        Self {
168            significand: IBig::ONE,
169            exponent: 0,
170        }
171    }
172    /// Create a [Repr] instance representing value negative one
173    #[inline]
174    pub const fn neg_one() -> Self {
175        Self {
176            significand: IBig::NEG_ONE,
177            exponent: 0,
178        }
179    }
180    /// Create a [Repr] instance representing the (positive) infinity
181    #[inline]
182    pub const fn infinity() -> Self {
183        Self {
184            significand: IBig::ZERO,
185            exponent: isize::MAX,
186        }
187    }
188    /// Create a [Repr] instance representing the negative infinity
189    #[inline]
190    pub const fn neg_infinity() -> Self {
191        Self {
192            significand: IBig::ZERO,
193            exponent: isize::MIN,
194        }
195    }
196    /// Create a [Repr] instance representing the negative zero (`-0`)
197    ///
198    /// Negative zero is produced by operations (e.g. `1 / -inf`, `ceil(-0)`, cancellation
199    /// under round-toward-negative) and is distinct from `+0` only in operations that are
200    /// sensitive to the sign of zero (e.g. `1 / -0 = -inf`). It compares equal to `+0`.
201    #[inline]
202    pub const fn neg_zero() -> Self {
203        Self {
204            significand: IBig::ZERO,
205            exponent: -1,
206        }
207    }
208
209    /// Determine if the [Repr] represents positive zero (`+0`)
210    ///
211    /// This returns `true` only for `+0`; use [`Self::is_neg_zero`] to detect `-0`, or check
212    /// `self.significand().is_zero()` to detect either signed zero.
213    ///
214    /// # Examples
215    ///
216    /// ```
217    /// # use dashu_float::Repr;
218    /// assert!(Repr::<2>::zero().is_pos_zero());
219    /// assert!(!Repr::<10>::neg_zero().is_pos_zero());
220    /// assert!(!Repr::<10>::one().is_pos_zero());
221    /// ```
222    #[inline]
223    pub const fn is_pos_zero(&self) -> bool {
224        self.significand.is_zero() && self.exponent == 0
225    }
226
227    /// Determine if the [Repr] represents the negative zero (`-0`)
228    ///
229    /// # Examples
230    ///
231    /// ```
232    /// # use dashu_float::Repr;
233    /// assert!(Repr::<2>::neg_zero().is_neg_zero());
234    /// assert!(!Repr::<10>::zero().is_neg_zero());
235    /// assert!(!Repr::<10>::one().is_neg_zero());
236    /// ```
237    #[inline]
238    pub const fn is_neg_zero(&self) -> bool {
239        self.significand.is_zero() && self.exponent == -1
240    }
241
242    /// Determine if the [Repr] represents one
243    ///
244    /// # Examples
245    ///
246    /// ```
247    /// # use dashu_float::Repr;
248    /// assert!(Repr::<2>::zero().is_pos_zero());
249    /// assert!(!Repr::<10>::one().is_pos_zero());
250    /// ```
251    #[inline]
252    pub const fn is_one(&self) -> bool {
253        self.significand.is_one() && self.exponent == 0
254    }
255
256    /// Determine if the [Repr] represents the (±)infinity
257    ///
258    /// # Examples
259    ///
260    /// ```
261    /// # use dashu_float::Repr;
262    /// assert!(Repr::<2>::infinity().is_infinite());
263    /// assert!(Repr::<10>::neg_infinity().is_infinite());
264    /// assert!(!Repr::<10>::one().is_infinite());
265    /// assert!(!Repr::<10>::neg_zero().is_infinite());
266    /// ```
267    #[inline]
268    pub const fn is_infinite(&self) -> bool {
269        self.significand.is_zero() && (self.exponent == isize::MAX || self.exponent == isize::MIN)
270    }
271
272    /// Determine if the [Repr] represents a finite number
273    ///
274    /// # Examples
275    ///
276    /// ```
277    /// # use dashu_float::Repr;
278    /// assert!(Repr::<2>::zero().is_finite());
279    /// assert!(Repr::<10>::one().is_finite());
280    /// assert!(!Repr::<16>::infinity().is_finite());
281    /// ```
282    #[inline]
283    pub const fn is_finite(&self) -> bool {
284        !self.is_infinite()
285    }
286
287    /// Determine if the number can be regarded as an integer.
288    ///
289    /// Note that this function returns false when the number is infinite.
290    ///
291    /// # Examples
292    ///
293    /// ```
294    /// # use dashu_float::Repr;
295    /// assert!(Repr::<2>::zero().is_int());
296    /// assert!(Repr::<10>::one().is_int());
297    /// assert!(!Repr::<16>::new(123.into(), -1).is_int());
298    /// ```
299    pub const fn is_int(&self) -> bool {
300        if self.is_infinite() {
301            false
302        } else {
303            self.exponent >= 0
304        }
305    }
306
307    /// Get the sign of the number
308    ///
309    /// Note that `-0` has a negative sign (so `1 / -0 = -inf`), while `+0` has a positive sign.
310    ///
311    /// # Examples
312    ///
313    /// ```
314    /// # use dashu_base::Sign;
315    /// # use dashu_float::Repr;
316    /// assert_eq!(Repr::<2>::zero().sign(), Sign::Positive);
317    /// assert_eq!(Repr::<2>::neg_zero().sign(), Sign::Negative);
318    /// assert_eq!(Repr::<2>::neg_one().sign(), Sign::Negative);
319    /// assert_eq!(Repr::<10>::neg_infinity().sign(), Sign::Negative);
320    /// ```
321    #[inline]
322    pub const fn sign(&self) -> Sign {
323        if self.significand.is_zero() {
324            if self.exponent >= 0 {
325                Sign::Positive
326            } else {
327                Sign::Negative
328            }
329        } else {
330            self.significand.sign()
331        }
332    }
333
334    /// Negate the number, correctly toggling the sign of `±0` and `±inf` by flipping the
335    /// special-value exponent (negating the significand alone is a no-op for zero).
336    #[inline]
337    pub(crate) fn neg(self) -> Self {
338        if self.significand.is_zero() {
339            Self {
340                significand: self.significand,
341                exponent: negate_special_exponent(self.exponent),
342            }
343        } else {
344            Self {
345                significand: -self.significand,
346                exponent: self.exponent,
347            }
348        }
349    }
350
351    /// Check that a `Repr` with a non-zero significand has a valid finite exponent.
352    ///
353    /// Returns [`FpError::Overflow`] or [`FpError::Underflow`] when the exponent collides with
354    /// the `+inf`/`-inf` sentinels (`isize::MAX` / `isize::MIN`). Zero-significand reprs
355    /// (canonical special values) always pass.
356    pub(crate) fn check_finite_exponent(self) -> Result<Self, FpError> {
357        if !self.significand.is_zero() {
358            if self.exponent == isize::MAX {
359                Err(FpError::Overflow(self.sign()))
360            } else if self.exponent == isize::MIN {
361                Err(FpError::Underflow(self.sign()))
362            } else {
363                Ok(self)
364            }
365        } else {
366            Ok(self)
367        }
368    }
369
370    /// Create the `Repr` for a signed infinity from the mathematical sign of a result that
371    /// overflowed.
372    #[inline]
373    pub(crate) const fn infinity_with_sign(sign: Sign) -> Self {
374        match sign {
375            Sign::Positive => Self::infinity(),
376            Sign::Negative => Self::neg_infinity(),
377        }
378    }
379
380    /// Create the `Repr` for a signed zero from the mathematical sign of a result that
381    /// underflowed.
382    #[inline]
383    pub(crate) const fn zero_with_sign(sign: Sign) -> Self {
384        match sign {
385            Sign::Positive => Self::zero(),
386            Sign::Negative => Self::neg_zero(),
387        }
388    }
389
390    /// Normalize the float representation so that the significand is not divisible by the base.
391    ///
392    /// A zero significand denotes a canonical special value (`+0`, `-0`, `+inf`, `-inf`) and is
393    /// returned unchanged; any other (non-canonical) zero significand is normalized to `+0`.
394    pub(crate) fn normalize(self) -> Self {
395        if self.significand.is_zero() {
396            // Preserve the four canonical special-value encodings; collapse anything else to +0.
397            if self.exponent == 0
398                || self.exponent == -1
399                || self.exponent == isize::MAX
400                || self.exponent == isize::MIN
401            {
402                return self;
403            }
404            return Self::zero();
405        }
406
407        let Self {
408            mut significand,
409            mut exponent,
410        } = self;
411        if B == 2 {
412            let shift = significand.trailing_zeros().unwrap();
413            significand >>= shift;
414            exponent = exponent.saturating_add(shift as isize);
415        } else if B.is_power_of_two() {
416            let bits = B.trailing_zeros() as usize;
417            let shift = significand.trailing_zeros().unwrap() / bits;
418            significand >>= shift * bits;
419            exponent = exponent.saturating_add(shift as isize);
420        } else {
421            let (sign, mut mag) = significand.into_parts();
422            let shift = mag.remove(&UBig::from_word(B)).unwrap();
423            exponent = exponent.saturating_add(shift as isize);
424            significand = IBig::from_parts(sign, mag);
425        }
426        Self {
427            significand,
428            exponent,
429        }
430    }
431
432    /// Get the number of digits (under base `B`) in the significand.
433    ///
434    /// If the number is 0, then 0 is returned (instead of 1).
435    ///
436    /// # Examples
437    ///
438    /// ```
439    /// # use dashu_float::Repr;
440    /// assert_eq!(Repr::<2>::zero().digits(), 0);
441    /// assert_eq!(Repr::<2>::one().digits(), 1);
442    /// assert_eq!(Repr::<10>::one().digits(), 1);
443    ///
444    /// assert_eq!(Repr::<10>::new(100.into(), 0).digits(), 1); // 1e2
445    /// assert_eq!(Repr::<10>::new(101.into(), 0).digits(), 3);
446    /// ```
447    #[inline]
448    pub fn digits(&self) -> usize {
449        assert_finite(self);
450        digit_len::<B>(&self.significand)
451    }
452
453    /// Fast over-estimation of [digits][Self::digits]
454    ///
455    /// # Examples
456    ///
457    /// ```
458    /// # use dashu_float::Repr;
459    /// assert_eq!(Repr::<2>::zero().digits_ub(), 0);
460    /// assert_eq!(Repr::<2>::one().digits_ub(), 1);
461    /// assert_eq!(Repr::<10>::one().digits_ub(), 1);
462    /// assert_eq!(Repr::<2>::new(31.into(), 0).digits_ub(), 5);
463    /// assert_eq!(Repr::<10>::new(99.into(), 0).digits_ub(), 2);
464    /// ```
465    #[inline]
466    pub fn digits_ub(&self) -> usize {
467        assert_finite(self);
468        if self.significand.is_zero() {
469            return 0;
470        }
471
472        let log = match B {
473            2 => self.significand.log2_bounds().1,
474            10 => self.significand.log2_bounds().1 * core::f32::consts::LOG10_2,
475            _ => self.significand.log2_bounds().1 / Self::BASE.log2_bounds().0,
476        };
477        log as usize + 1
478    }
479
480    /// Fast under-estimation of [digits][Self::digits]
481    ///
482    /// # Examples
483    ///
484    /// ```
485    /// # use dashu_float::Repr;
486    /// assert_eq!(Repr::<2>::zero().digits_lb(), 0);
487    /// assert_eq!(Repr::<2>::one().digits_lb(), 0);
488    /// assert_eq!(Repr::<10>::one().digits_lb(), 0);
489    /// assert!(Repr::<10>::new(1001.into(), 0).digits_lb() <= 3);
490    /// ```
491    #[inline]
492    pub fn digits_lb(&self) -> usize {
493        assert_finite(self);
494        if self.significand.is_zero() {
495            return 0;
496        }
497
498        let log = match B {
499            2 => self.significand.log2_bounds().0,
500            10 => self.significand.log2_bounds().0 * core::f32::consts::LOG10_2,
501            _ => self.significand.log2_bounds().0 / Self::BASE.log2_bounds().1,
502        };
503        log as usize
504    }
505
506    /// Quickly test if `|self| < 1`. IT's not always correct,
507    /// but there are guaranteed to be no false postives.
508    #[inline]
509    pub(crate) fn smaller_than_one(&self) -> bool {
510        debug_assert!(self.is_finite());
511        self.exponent + (self.digits_ub() as isize) < -1
512    }
513
514    /// Create a [Repr] from the significand and exponent. This
515    /// constructor will normalize the representation.
516    ///
517    /// # Examples
518    ///
519    /// ```
520    /// # use dashu_int::IBig;
521    /// # use dashu_float::Repr;
522    /// let a = Repr::<2>::new(400.into(), -2);
523    /// assert_eq!(a.significand(), &IBig::from(25));
524    /// assert_eq!(a.exponent(), 2);
525    ///
526    /// let b = Repr::<10>::new(400.into(), -2);
527    /// assert_eq!(b.significand(), &IBig::from(4));
528    /// assert_eq!(b.exponent(), 0);
529    /// ```
530    #[inline]
531    pub fn new(significand: IBig, exponent: isize) -> Self {
532        Self {
533            significand,
534            exponent,
535        }
536        .normalize()
537    }
538
539    /// Create a normalized [`Repr`] from a signed [`DoubleWord`] significand and an exponent.
540    ///
541    /// This is the const-evaluable counterpart of [`Repr::new`]: because it operates on a
542    /// [`DoubleWord`] significand it needs no heap `IBig` arithmetic, so it is usable in `const`
543    /// contexts — it is what the complex literal macros build on. As with [`Repr::new`], the
544    /// significand is normalized (trailing zero digits in base `B` are folded into the exponent).
545    /// A zero significand yields [`Repr::zero`] regardless of sign.
546    ///
547    /// Unlike [`Repr::new`], this does not report the digit count (computing it would require
548    /// returning it alongside the `Repr`, which can't be destructured in a `const fn`); callers
549    /// that need a precision should pass it explicitly.
550    ///
551    /// # Examples
552    ///
553    /// ```
554    /// use dashu_base::Sign;
555    /// use dashu_float::Repr;
556    /// use dashu_int::IBig;
557    ///
558    /// let r = Repr::<10>::new_const(Sign::Positive, 123400, -2);
559    /// assert_eq!(r.significand(), &IBig::from(1234));
560    /// assert_eq!(r.exponent(), 0);
561    /// ```
562    #[inline]
563    pub const fn new_const(sign: Sign, significand: DoubleWord, exponent: isize) -> Self {
564        let (significand, exponent, _) = normalize_word_const::<B>(significand, exponent);
565        if significand == 0 {
566            Self::zero()
567        } else {
568            Self {
569                significand: IBig::from_parts_const(sign, significand),
570                exponent,
571            }
572        }
573    }
574
575    /// Get the significand of the representation
576    #[inline]
577    pub fn significand(&self) -> &IBig {
578        &self.significand
579    }
580
581    /// Get the exponent of the representation
582    #[inline]
583    pub fn exponent(&self) -> isize {
584        self.exponent
585    }
586
587    /// Convert the float number into raw `(signficand, exponent)` parts
588    ///
589    /// # Examples
590    ///
591    /// ```
592    /// # use dashu_float::Repr;
593    /// use dashu_int::IBig;
594    ///
595    /// let a = Repr::<2>::new(400.into(), -2);
596    /// assert_eq!(a.into_parts(), (IBig::from(25), 2));
597    ///
598    /// let b = Repr::<10>::new(400.into(), -2);
599    /// assert_eq!(b.into_parts(), (IBig::from(4), 0));
600    /// ```
601    #[inline]
602    pub fn into_parts(self) -> (IBig, isize) {
603        (self.significand, self.exponent)
604    }
605
606    /// Create an Repr from a static sequence of [Word][crate::Word]s representing the significand.
607    ///
608    /// This method is intended for static creation macros.
609    #[doc(hidden)]
610    #[rustversion::since(1.64)]
611    #[inline]
612    pub const unsafe fn from_static_words(
613        sign: Sign,
614        significand: &'static [Word],
615        exponent: isize,
616    ) -> Self {
617        let significand = IBig::from_static_words(sign, significand);
618        assert!(!significand.is_multiple_of_const(B as _));
619
620        Self {
621            significand,
622            exponent,
623        }
624    }
625}
626
627// This custom implementation is necessary due to https://github.com/rust-lang/rust/issues/98374
628impl<const B: Word> Clone for Repr<B> {
629    #[inline]
630    fn clone(&self) -> Self {
631        Self {
632            significand: self.significand.clone(),
633            exponent: self.exponent,
634        }
635    }
636
637    #[inline]
638    fn clone_from(&mut self, source: &Self) {
639        self.significand.clone_from(&source.significand);
640        self.exponent = source.exponent;
641    }
642}
643
644impl<R: Round> Context<R> {
645    /// Create a float operation context with the given precision limit.
646    #[inline]
647    pub const fn new(precision: usize) -> Self {
648        Self {
649            precision,
650            _marker: PhantomData,
651        }
652    }
653
654    /// Create a float operation context with the higher precision from the two context inputs.
655    ///
656    /// # Examples
657    ///
658    /// ```
659    /// use dashu_float::{Context, round::mode::Zero};
660    ///
661    /// let ctxt1 = Context::<Zero>::new(2);
662    /// let ctxt2 = Context::<Zero>::new(5);
663    /// assert_eq!(Context::max(ctxt1, ctxt2).precision(), 5);
664    /// ```
665    #[inline]
666    pub const fn max(lhs: Self, rhs: Self) -> Self {
667        Self {
668            // this comparison also correctly handles ulimited precisions (precision = 0)
669            precision: if lhs.precision > rhs.precision {
670                lhs.precision
671            } else {
672                rhs.precision
673            },
674            _marker: PhantomData,
675        }
676    }
677
678    /// Check whether the precision is limited (not zero)
679    #[inline]
680    pub(crate) const fn is_limited(&self) -> bool {
681        self.precision != 0
682    }
683
684    /// Get the precision limited from the context
685    #[inline]
686    pub const fn precision(&self) -> usize {
687        self.precision
688    }
689
690    /// `⌈log_B(precision)⌉` — the number of base-`B` digits needed to index the precision word.
691    ///
692    /// This is the base guard every transcendental Ziv loop adds on top of `precision`: it absorbs
693    /// the rounding that accumulates over the `O(log p)` series / squaring steps. Each caller adds
694    /// its own operation-specific constant (`+ 2`, `+ 10`, …); this helper is the shared core that
695    /// maps the binary precision estimate onto the output base.
696    pub(crate) fn base_guard_digits<const B: Word>(&self) -> usize {
697        ceil_usize(self.precision.log2_est() / B.log2_est())
698    }
699
700    /// Round the repr to the desired precision
701    pub(crate) fn repr_round<const B: Word>(&self, repr: Repr<B>) -> Rounded<Repr<B>> {
702        assert_finite(&repr);
703        if !self.is_limited() {
704            return Exact(repr);
705        }
706
707        let digits = repr.digits();
708        if digits > self.precision {
709            let shift = digits - self.precision;
710            let input_neg = repr.sign() == Sign::Negative;
711            let (signif_hi, signif_lo) = split_digits::<B>(repr.significand, shift);
712            let adjust = R::round_fract::<B>(&signif_hi, signif_lo, shift);
713            let sig = signif_hi + adjust;
714            let result = rounded_to_repr(sig, repr.exponent + shift as isize, input_neg);
715            Inexact(result, adjust)
716        } else {
717            Exact(repr)
718        }
719    }
720
721    /// Round the repr to the desired precision
722    pub(crate) fn repr_round_ref<const B: Word>(&self, repr: &Repr<B>) -> Rounded<Repr<B>> {
723        assert_finite(repr);
724        if !self.is_limited() {
725            return Exact(repr.clone());
726        }
727
728        let digits = repr.digits();
729        if digits > self.precision {
730            let shift = digits - self.precision;
731            let input_neg = repr.sign() == Sign::Negative;
732            let (signif_hi, signif_lo) = split_digits_ref::<B>(&repr.significand, shift);
733            let adjust = R::round_fract::<B>(&signif_hi, signif_lo, shift);
734            let sig = signif_hi + adjust;
735            let result = rounded_to_repr(sig, repr.exponent + shift as isize, input_neg);
736            Inexact(result, adjust)
737        } else {
738            Exact(repr.clone())
739        }
740    }
741}
742
743#[cfg(test)]
744mod tests {
745    use super::*;
746    use dashu_base::Sign;
747
748    #[test]
749    fn infinity_encoding() {
750        assert_eq!(Repr::<2>::infinity().exponent, isize::MAX);
751        assert_eq!(Repr::<10>::neg_infinity().exponent, isize::MIN);
752        assert!(Repr::<2>::infinity().is_infinite());
753        assert!(Repr::<10>::neg_infinity().is_infinite());
754        assert!(!Repr::<2>::infinity().is_finite());
755        assert_eq!(Repr::<2>::infinity().sign(), Sign::Positive);
756        assert_eq!(Repr::<10>::neg_infinity().sign(), Sign::Negative);
757    }
758
759    #[test]
760    fn neg_zero_encoding() {
761        assert_eq!(Repr::<2>::neg_zero().exponent, -1);
762        assert!(Repr::<2>::neg_zero().is_neg_zero());
763        assert!(!Repr::<2>::neg_zero().is_pos_zero());
764        assert!(!Repr::<2>::neg_zero().is_infinite());
765        assert_eq!(Repr::<2>::neg_zero().sign(), Sign::Negative);
766        assert_eq!(Repr::<2>::zero().sign(), Sign::Positive);
767    }
768
769    #[test]
770    fn normalize_preserves_specials() {
771        // infinities are preserved (the previous clobbering bug)
772        assert_eq!(Repr::<2>::infinity(), Repr::<2>::infinity().normalize());
773        assert_eq!(Repr::<10>::neg_infinity(), Repr::<10>::neg_infinity().normalize());
774        // +0 is preserved
775        assert_eq!(Repr::<2>::zero(), Repr::<2>::zero().normalize());
776        // a stray zero significand with a non-sentinel exponent collapses to +0
777        let stray: Repr<2> = Repr {
778            significand: IBig::ZERO,
779            exponent: 7,
780        };
781        assert_eq!(Repr::<2>::zero(), stray.normalize());
782        // non-zero significands are still normalized
783        let r: Repr<2> = Repr {
784            significand: IBig::from(0b10100i32),
785            exponent: 0,
786        };
787        let r = r.normalize();
788        assert_eq!(r.significand, IBig::from(0b101i32));
789        assert_eq!(r.exponent, 2);
790    }
791}