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