Skip to main content

oxinum_float/native/
float.rs

1//! Native `BigFloat` — binary-base arbitrary-precision floating-point
2//! number built as `(sign, mantissa, exponent, precision)`.
3//!
4//! The value of a `BigFloat` is
5//!
6//! ```text
7//! (-1)^sign * mantissa * 2^exponent
8//! ```
9//!
10//! where `mantissa` is a non-negative [`BigUint`] and `exponent` is a signed
11//! 64-bit integer.
12//!
13//! # Invariants
14//!
15//! Every public constructor and arithmetic operation re-establishes the
16//! following invariants:
17//!
18//! 1. `precision > 0`.
19//! 2. If `mantissa.is_zero()` then the value is the canonical zero at
20//!    `precision`: `{ Positive, 0, 0, precision }`.
21//! 3. If `!mantissa.is_zero()` then the mantissa is *normalized*:
22//!    `mantissa.bit_length() == precision` (the top bit is set).
23//!
24//! Two non-zero `BigFloat` values compare equal iff their `(sign, mantissa,
25//! exponent)` triples match. **Precision is excluded from equality**: it is
26//! a representation knob, not part of the mathematical value. A zero at
27//! precision 10 equals a zero at precision 50.
28
29use core::cmp::Ordering;
30use core::fmt;
31
32use oxinum_core::Sign;
33use oxinum_int::native::BigUint;
34
35/// Classification of a `BigFloat` value: finite, infinite, or NaN.
36///
37/// The sign of ±Inf is carried by the `BigFloat::sign` field. NaN has a
38/// single canonical form (`sign = Positive`).
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
40pub enum FloatClass {
41    #[default]
42    Finite,
43    Infinite,
44    Nan,
45}
46
47/// Rounding modes for native `BigFloat` arithmetic.
48///
49/// Mirrors the set of rounding policies natively supported by the binary
50/// `BigFloat` core. The seven variants cover all IEEE-754 directed and
51/// nearest-tie-break combinations.
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
53pub enum RoundingMode {
54    /// Round half to even (banker's rounding).
55    HalfEven,
56    /// Round half away from zero.
57    HalfAway,
58    /// Round half toward zero (truncate ties).
59    HalfToZero,
60    /// Truncate toward zero (drop fractional bits).
61    ToZero,
62    /// Round toward `+∞`.
63    ToInf,
64    /// Round toward `-∞`.
65    ToNegInf,
66    /// Round away from zero (round up in magnitude).
67    AwayFromZero,
68}
69
70/// Native arbitrary-precision binary float.
71///
72/// `BigFloat` represents `(-1)^sign * mantissa * 2^exponent` with `precision`
73/// significant bits. See the module-level documentation for the full
74/// invariant list.
75///
76/// # Examples
77///
78/// ```
79/// use oxinum_float::native::{BigFloat, RoundingMode};
80///
81/// let a = BigFloat::from_i64(3, 8, RoundingMode::HalfEven);
82/// let b = BigFloat::from_i64(5, 8, RoundingMode::HalfEven);
83/// let sum = &a + &b;
84/// assert_eq!(sum.to_f64(), 8.0);
85/// ```
86#[derive(Clone)]
87pub struct BigFloat {
88    pub(crate) class: FloatClass,
89    pub(crate) sign: Sign,
90    pub(crate) mantissa: BigUint,
91    pub(crate) exponent: i64,
92    pub(crate) precision: u32,
93}
94
95impl BigFloat {
96    /// Canonical zero at `prec` bits of precision.
97    ///
98    /// # Panics
99    ///
100    /// Panics if `prec == 0` (the precision invariant requires `prec > 0`).
101    ///
102    /// # Examples
103    ///
104    /// ```
105    /// use oxinum_float::native::BigFloat;
106    /// let z = BigFloat::zero(53);
107    /// assert!(z.is_zero());
108    /// assert_eq!(z.precision(), 53);
109    /// ```
110    pub fn zero(prec: u32) -> Self {
111        assert!(prec > 0, "BigFloat precision must be > 0");
112        Self {
113            class: FloatClass::Finite,
114            sign: Sign::Positive,
115            mantissa: BigUint::zero(),
116            exponent: 0,
117            precision: prec,
118        }
119    }
120
121    /// Create a canonical NaN at `prec` bits. NaN's sign is always `Positive`.
122    pub fn nan(prec: u32) -> Self {
123        assert!(prec > 0, "BigFloat precision must be > 0");
124        Self {
125            class: FloatClass::Nan,
126            sign: Sign::Positive,
127            mantissa: BigUint::zero(),
128            exponent: 0,
129            precision: prec,
130        }
131    }
132
133    /// Create positive infinity (`+∞`) at `prec` bits.
134    pub fn infinity(prec: u32) -> Self {
135        assert!(prec > 0, "BigFloat precision must be > 0");
136        Self {
137            class: FloatClass::Infinite,
138            sign: Sign::Positive,
139            mantissa: BigUint::zero(),
140            exponent: 0,
141            precision: prec,
142        }
143    }
144
145    /// Create negative infinity (`−∞`) at `prec` bits.
146    pub fn neg_infinity(prec: u32) -> Self {
147        assert!(prec > 0, "BigFloat precision must be > 0");
148        Self {
149            class: FloatClass::Infinite,
150            sign: Sign::Negative,
151            mantissa: BigUint::zero(),
152            exponent: 0,
153            precision: prec,
154        }
155    }
156
157    /// Construct directly from already-validated parts.
158    ///
159    /// The result is normalized (trailing-zero bits migrated into the
160    /// exponent) and then rounded to `prec` bits if the normalized mantissa
161    /// is wider than `prec`. If the normalized mantissa is narrower, it is
162    /// left-padded so `mantissa.bit_length() == prec` while preserving the
163    /// mathematical value.
164    ///
165    /// Used by every higher-level constructor (`from_i64`, `from_f64`,
166    /// arithmetic) — call this whenever you need to land back at the
167    /// canonical invariant from arbitrary parts.
168    pub fn from_parts(
169        sign: Sign,
170        mantissa: BigUint,
171        exponent: i64,
172        prec: u32,
173        mode: RoundingMode,
174    ) -> Self {
175        assert!(prec > 0, "BigFloat precision must be > 0");
176        if mantissa.is_zero() {
177            return Self::zero(prec);
178        }
179        let mut out = Self {
180            class: FloatClass::Finite,
181            sign,
182            mantissa,
183            exponent,
184            precision: prec,
185        };
186        out.canonicalize_normalize();
187        out.round_to_precision_in_place(prec, mode);
188        out
189    }
190
191    /// Returns the precision in bits.
192    #[inline]
193    pub fn precision(&self) -> u32 {
194        self.precision
195    }
196
197    /// Returns the sign.
198    ///
199    /// For the canonical zero, the sign is always [`Sign::Positive`].
200    #[inline]
201    pub fn sign(&self) -> Sign {
202        self.sign
203    }
204
205    /// Returns a reference to the mantissa.
206    #[inline]
207    pub fn mantissa(&self) -> &BigUint {
208        &self.mantissa
209    }
210
211    /// Returns the binary exponent (the power of 2 the mantissa is scaled by).
212    #[inline]
213    pub fn exponent(&self) -> i64 {
214        self.exponent
215    }
216
217    /// Returns `true` if this value is the canonical zero.
218    ///
219    /// NaN and Inf have `mantissa = 0` internally, so this check requires
220    /// testing the `class` field first.
221    #[inline]
222    pub fn is_zero(&self) -> bool {
223        matches!(self.class, FloatClass::Finite) && self.mantissa.is_zero()
224    }
225
226    /// Returns `true` if this value is finite (not NaN or Inf).
227    #[inline]
228    pub fn is_finite(&self) -> bool {
229        matches!(self.class, FloatClass::Finite)
230    }
231
232    /// Returns `true` if this value is infinite (`+∞` or `−∞`).
233    #[inline]
234    pub fn is_infinite(&self) -> bool {
235        matches!(self.class, FloatClass::Infinite)
236    }
237
238    /// Returns `true` if this value is NaN.
239    #[inline]
240    pub fn is_nan(&self) -> bool {
241        matches!(self.class, FloatClass::Nan)
242    }
243
244    /// Returns `true` for finite non-zero values.
245    ///
246    /// Arbitrary-precision floats have no `Subnormal` category — every nonzero
247    /// finite value is `Normal`.
248    #[inline]
249    pub fn is_normal(&self) -> bool {
250        matches!(self.class, FloatClass::Finite) && !self.mantissa.is_zero()
251    }
252
253    /// Returns the IEEE 754 float class.
254    ///
255    /// `FpCategory::Subnormal` is never returned: there is no fixed exponent
256    /// range in arbitrary-precision arithmetic, so every nonzero finite value
257    /// is `Normal`.
258    pub fn classify(&self) -> core::num::FpCategory {
259        use core::num::FpCategory;
260        match self.class {
261            FloatClass::Nan => FpCategory::Nan,
262            FloatClass::Infinite => FpCategory::Infinite,
263            FloatClass::Finite if self.mantissa.is_zero() => FpCategory::Zero,
264            FloatClass::Finite => FpCategory::Normal,
265        }
266    }
267
268    /// Returns `true` for positive and NaN values (NaN has canonical positive sign).
269    ///
270    /// Note: unlike `f64`, the single canonical zero has `is_sign_positive() == true`.
271    #[inline]
272    pub fn is_sign_positive(&self) -> bool {
273        self.sign == Sign::Positive
274    }
275
276    /// Returns `true` only for negative-sign values (negative Inf or negative finite).
277    ///
278    /// Note: canonical zero has `is_sign_negative() == false` (no signed zero).
279    #[inline]
280    pub fn is_sign_negative(&self) -> bool {
281        self.sign == Sign::Negative
282    }
283
284    /// Returns `-1`, `0`, or `+1` depending on the sign of the value.
285    pub fn signum(&self) -> i32 {
286        if self.is_zero() {
287            0
288        } else if self.sign == Sign::Negative {
289            -1
290        } else {
291            1
292        }
293    }
294
295    /// Returns the absolute value (sign forced to [`Sign::Positive`]).
296    pub fn abs(&self) -> Self {
297        let mut out = self.clone();
298        out.sign = Sign::Positive;
299        out
300    }
301
302    /// Returns the additive inverse.
303    ///
304    /// Negating the canonical zero yields the canonical zero (sign stays
305    /// `Positive`). Negating NaN returns NaN unchanged (the canonical NaN
306    /// always has sign `Positive`).
307    pub fn neg(&self) -> Self {
308        // NaN: canonical NaN is always sign-positive; negating NaN returns NaN unchanged.
309        if self.is_nan() {
310            return self.clone();
311        }
312        if self.is_zero() {
313            return self.clone();
314        }
315        let mut out = self.clone();
316        out.sign = match self.sign {
317            Sign::Positive => Sign::Negative,
318            Sign::Negative => Sign::Positive,
319        };
320        out
321    }
322
323    /// Change the precision, rounding the mantissa with `mode` if narrowing.
324    ///
325    /// # Examples
326    ///
327    /// ```
328    /// use oxinum_float::native::{BigFloat, RoundingMode};
329    /// let a = BigFloat::from_i64(7, 8, RoundingMode::HalfEven);
330    /// let b = a.with_precision(64, RoundingMode::HalfEven);
331    /// assert_eq!(b.precision(), 64);
332    /// assert_eq!(b.to_f64(), 7.0);
333    /// ```
334    #[must_use]
335    pub fn with_precision(self, prec: u32, mode: RoundingMode) -> Self {
336        self.round_to_precision(prec, mode)
337    }
338
339    /// Round the mantissa so that, after normalization, it has exactly `prec`
340    /// significant bits.
341    ///
342    /// If the current mantissa has fewer bits than `prec`, the result is
343    /// left-padded; the mathematical value is unchanged. If it has more bits,
344    /// the low bits are discarded according to `mode`.
345    #[must_use]
346    pub fn round_to_precision(mut self, prec: u32, mode: RoundingMode) -> Self {
347        self.round_to_precision_in_place(prec, mode);
348        self
349    }
350
351    /// In-place version of [`Self::round_to_precision`].
352    pub(crate) fn round_to_precision_in_place(&mut self, prec: u32, mode: RoundingMode) {
353        assert!(prec > 0, "BigFloat precision must be > 0");
354        // Non-finite values (NaN, ±Inf) have no mantissa to round; just update precision.
355        if !self.is_finite() {
356            self.precision = prec;
357            return;
358        }
359        self.precision = prec;
360        if self.mantissa.is_zero() {
361            // Canonical zero — nothing to round, just adopt the new precision.
362            self.sign = Sign::Positive;
363            self.exponent = 0;
364            return;
365        }
366        // Strip trailing zero bits into the exponent so the operation is
367        // performed on the unique normalized representation.
368        self.absorb_trailing_zeros();
369
370        let cur_bits = self.mantissa.bit_length();
371        let target = prec as u64;
372        match cur_bits.cmp(&target) {
373            Ordering::Less => {
374                // Pad left — value preserved exactly.
375                let shift = target - cur_bits;
376                self.mantissa = self.mantissa.shl_bits(shift);
377                // Underflow guard: shifting left lowers exponent.
378                // i64::MIN minus a positive number would saturate, but for
379                // realistic precisions (prec <= u32::MAX) this is far from
380                // the boundary. Defensive saturating sub keeps us safe.
381                self.exponent = self.exponent.saturating_sub(shift as i64);
382            }
383            Ordering::Equal => { /* already normalized */ }
384            Ordering::Greater => {
385                let drop = cur_bits - target;
386                self.round_drop_low_bits(drop, mode);
387            }
388        }
389        debug_assert!(
390            self.mantissa.is_zero() || self.mantissa.bit_length() == self.precision as u64,
391            "BigFloat normalize invariant violated after round_to_precision",
392        );
393        debug_assert!(
394            !self.mantissa.is_zero() || self.sign == Sign::Positive,
395            "BigFloat canonical-zero invariant violated",
396        );
397    }
398
399    // -----------------------------------------------------------------------
400    // Internal: invariant-establishing helpers
401    // -----------------------------------------------------------------------
402
403    /// Strip trailing-zero bits from `mantissa` and add their count to
404    /// `exponent`. No-op when the mantissa is zero.
405    pub(crate) fn absorb_trailing_zeros(&mut self) {
406        if self.mantissa.is_zero() {
407            return;
408        }
409        let tz = self.mantissa.trailing_zeros();
410        if tz > 0 {
411            self.mantissa = self.mantissa.shr_bits(tz);
412            // Adding a non-negative value to a possibly negative exponent.
413            // Defensive saturating_add keeps us safe against pathological
414            // mantissas with billions of trailing zeros.
415            self.exponent = self.exponent.saturating_add(tz as i64);
416        }
417    }
418
419    /// Canonicalize by stripping trailing zeros and (if needed) padding the
420    /// mantissa to `self.precision` bits. After this call, either
421    /// `mantissa.is_zero()` (and the value is canonical zero) or
422    /// `mantissa.bit_length() == self.precision`.
423    ///
424    /// Does **not** round: assumes the caller is willing to grow the mantissa
425    /// to the requested precision exactly.
426    pub(crate) fn canonicalize_normalize(&mut self) {
427        if self.mantissa.is_zero() {
428            self.sign = Sign::Positive;
429            self.exponent = 0;
430            return;
431        }
432        self.absorb_trailing_zeros();
433        let cur_bits = self.mantissa.bit_length();
434        let target = self.precision as u64;
435        if cur_bits < target {
436            let shift = target - cur_bits;
437            self.mantissa = self.mantissa.shl_bits(shift);
438            self.exponent = self.exponent.saturating_sub(shift as i64);
439        }
440        // If cur_bits > target the caller (round_to_precision_in_place) handles
441        // the truncation; otherwise we have an exact normalized representation.
442    }
443
444    /// Drop the `drop` least-significant bits of `mantissa`, applying the
445    /// chosen rounding mode. Updates `exponent` accordingly. The result is
446    /// then re-normalized to satisfy the precision invariant.
447    fn round_drop_low_bits(&mut self, drop: u64, mode: RoundingMode) {
448        debug_assert!(drop > 0);
449        // Split mantissa = quotient * 2^drop + remainder.
450        // quotient = mantissa >> drop
451        // round_bit = bit (drop-1) of original mantissa
452        // sticky    = OR of bits 0..(drop-1) of original mantissa
453        let round_bit = self.mantissa.test_bit(drop - 1);
454        // Sticky = does any lower bit (below round_bit) survive?
455        let sticky = if drop >= 2 {
456            // Detect via comparing mantissa to (quotient << drop | round_bit << (drop-1))
457            // Cheaper: a value with the bottom (drop-1) bits zeroed is just
458            // (mantissa >> (drop-1)) << (drop-1). If that doesn't equal mantissa
459            // when restricted below the round bit, we have stickiness.
460            // Concretely: sticky iff mantissa.trailing_zeros() < drop - 1.
461            (self.mantissa.trailing_zeros()) < (drop - 1)
462        } else {
463            false
464        };
465        let mut quotient = self.mantissa.shr_bits(drop);
466        // Determine increment based on mode.
467        let negative = self.sign == Sign::Negative;
468        let increment = match mode {
469            RoundingMode::ToZero => false,
470            RoundingMode::AwayFromZero => round_bit || sticky,
471            RoundingMode::ToInf => !negative && (round_bit || sticky),
472            RoundingMode::ToNegInf => negative && (round_bit || sticky),
473            RoundingMode::HalfAway => round_bit,
474            RoundingMode::HalfToZero => round_bit && sticky,
475            RoundingMode::HalfEven => {
476                if !round_bit {
477                    false
478                } else if sticky {
479                    true
480                } else {
481                    // Exact half — round to even (LSB of quotient = 0).
482                    quotient.test_bit(0)
483                }
484            }
485        };
486        if increment {
487            let one = BigUint::one();
488            quotient = &quotient + &one;
489        }
490        self.exponent = self.exponent.saturating_add(drop as i64);
491        self.mantissa = quotient;
492        if self.mantissa.is_zero() {
493            // Whole number rounded to zero (e.g. all bits below round bit
494            // forming an exact half rounding toward zero). Canonical zero
495            // recapture.
496            self.sign = Sign::Positive;
497            self.exponent = 0;
498            return;
499        }
500        // Re-normalize: rounding may have grown the mantissa by one bit
501        // (carry on increment) or, in pathological cases, leave a sub-target
502        // bit width.
503        let cur_bits = self.mantissa.bit_length();
504        let target = self.precision as u64;
505        match cur_bits.cmp(&target) {
506            Ordering::Equal => {}
507            Ordering::Greater => {
508                // Carry overflowed by exactly one bit. Shift right one and
509                // bump the exponent.
510                let extra = cur_bits - target;
511                debug_assert_eq!(
512                    extra, 1,
513                    "rounding increment should overflow by at most one bit"
514                );
515                self.mantissa = self.mantissa.shr_bits(extra);
516                self.exponent = self.exponent.saturating_add(extra as i64);
517                // The newly-introduced trailing zero is allowed: the value is
518                // still normalized at the requested precision.
519            }
520            Ordering::Less => {
521                let shift = target - cur_bits;
522                self.mantissa = self.mantissa.shl_bits(shift);
523                self.exponent = self.exponent.saturating_sub(shift as i64);
524            }
525        }
526    }
527}
528
529// ---------------------------------------------------------------------------
530// Equality and ordering — NaN-aware (IEEE 754)
531//
532// `Eq` and `Ord` are NOT implemented: NaN breaks reflexivity (`NaN != NaN`)
533// and totality. Use `partial_cmp` / `partial_ord` for IEEE comparisons, or
534// `total_cmp` for a sort-stable total order.
535// ---------------------------------------------------------------------------
536
537impl PartialEq for BigFloat {
538    fn eq(&self, other: &Self) -> bool {
539        match (self.class, other.class) {
540            // NaN never equals anything, including itself.
541            (FloatClass::Nan, _) | (_, FloatClass::Nan) => false,
542            // Infinities: equal iff same sign.
543            (FloatClass::Infinite, FloatClass::Infinite) => self.sign == other.sign,
544            (FloatClass::Infinite, _) | (_, FloatClass::Infinite) => false,
545            // Both finite: precision-independent value equality.
546            (FloatClass::Finite, FloatClass::Finite) => {
547                if self.is_zero() && other.is_zero() {
548                    return true;
549                }
550                if self.is_zero() != other.is_zero() {
551                    return false;
552                }
553                self.sign == other.sign
554                    && self.exponent == other.exponent
555                    && self.mantissa == other.mantissa
556            }
557        }
558    }
559}
560
561impl BigFloat {
562    /// Compare the mathematical values of two **finite** `BigFloat`s.
563    ///
564    /// Caller must ensure both `self` and `other` are `Finite`.
565    pub(crate) fn cmp_finite(&self, other: &Self) -> Ordering {
566        match (self.is_zero(), other.is_zero()) {
567            (true, true) => return Ordering::Equal,
568            (true, false) => {
569                return if other.sign == Sign::Negative {
570                    Ordering::Greater
571                } else {
572                    Ordering::Less
573                };
574            }
575            (false, true) => {
576                return if self.sign == Sign::Negative {
577                    Ordering::Less
578                } else {
579                    Ordering::Greater
580                };
581            }
582            (false, false) => {}
583        }
584        match (self.sign, other.sign) {
585            (Sign::Positive, Sign::Negative) => Ordering::Greater,
586            (Sign::Negative, Sign::Positive) => Ordering::Less,
587            (Sign::Positive, Sign::Positive) => cmp_magnitudes(self, other),
588            (Sign::Negative, Sign::Negative) => cmp_magnitudes(other, self),
589        }
590    }
591
592    /// IEEE 754-style total order, suitable for sorting.
593    ///
594    /// Sequence: `−Inf` < (negative finite) < zero < (positive finite) < `+Inf` < `NaN`.
595    ///
596    /// Because this type has a single canonical zero and a single canonical NaN,
597    /// `total_cmp(NaN, NaN) == Equal` and `total_cmp(+Inf, NaN) == Less`.
598    pub fn total_cmp(&self, other: &Self) -> Ordering {
599        fn rank(x: &BigFloat) -> u8 {
600            match x.class {
601                FloatClass::Infinite if x.sign == Sign::Negative => 0,
602                FloatClass::Finite => 1,
603                FloatClass::Infinite => 2, // +Inf
604                FloatClass::Nan => 3,
605            }
606        }
607        let (ra, rb) = (rank(self), rank(other));
608        if ra != rb {
609            return ra.cmp(&rb);
610        }
611        match self.class {
612            FloatClass::Finite => self.cmp_finite(other),
613            _ => Ordering::Equal, // same non-finite rank → equal
614        }
615    }
616}
617
618impl PartialOrd for BigFloat {
619    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
620        match (self.class, other.class) {
621            // NaN is unordered with everything.
622            (FloatClass::Nan, _) | (_, FloatClass::Nan) => None,
623            // Inf vs Inf: same sign → Equal; otherwise ± ordering.
624            (FloatClass::Infinite, FloatClass::Infinite) => Some(match (self.sign, other.sign) {
625                (Sign::Positive, Sign::Positive) | (Sign::Negative, Sign::Negative) => {
626                    Ordering::Equal
627                }
628                (Sign::Negative, Sign::Positive) => Ordering::Less,
629                (Sign::Positive, Sign::Negative) => Ordering::Greater,
630            }),
631            // Inf vs finite.
632            (FloatClass::Infinite, FloatClass::Finite) => Some(if self.sign == Sign::Negative {
633                Ordering::Less
634            } else {
635                Ordering::Greater
636            }),
637            (FloatClass::Finite, FloatClass::Infinite) => Some(if other.sign == Sign::Negative {
638                Ordering::Greater
639            } else {
640                Ordering::Less
641            }),
642            // Both finite.
643            (FloatClass::Finite, FloatClass::Finite) => Some(self.cmp_finite(other)),
644        }
645    }
646}
647
648/// Compare the absolute values of two non-zero, normalized `BigFloat`s.
649///
650/// The normalization invariant lets us short-circuit on the *effective top
651/// bit position* (`exponent + precision`) before falling back on a mantissa
652/// comparison.
653pub(crate) fn cmp_magnitudes(a: &BigFloat, b: &BigFloat) -> Ordering {
654    // Effective top-bit position = exponent + (bit_length - 1).
655    // Both are non-zero and normalized => bit_length == precision.
656    // Compare top-bit positions first, then mantissas at common alignment.
657    let top_a = a
658        .exponent
659        .saturating_add(a.mantissa.bit_length() as i64 - 1);
660    let top_b = b
661        .exponent
662        .saturating_add(b.mantissa.bit_length() as i64 - 1);
663    match top_a.cmp(&top_b) {
664        Ordering::Equal => {
665            // Same magnitude order — align mantissas to the smaller exponent
666            // by shifting up the larger-exp mantissa.
667            if a.exponent >= b.exponent {
668                let shift = (a.exponent - b.exponent) as u64;
669                let lhs = a.mantissa.shl_bits(shift);
670                lhs.cmp(&b.mantissa)
671            } else {
672                let shift = (b.exponent - a.exponent) as u64;
673                let rhs = b.mantissa.shl_bits(shift);
674                a.mantissa.cmp(&rhs)
675            }
676        }
677        non_eq => non_eq,
678    }
679}
680
681// ---------------------------------------------------------------------------
682// Hex-float Display ("0xb<binary>p<exp>")
683// ---------------------------------------------------------------------------
684
685impl fmt::Display for BigFloat {
686    /// Hex-float-ish display. Always exact, always short:
687    ///
688    /// `<sign>0xb<binary-mantissa>p<exponent>`
689    ///
690    /// where `<binary-mantissa>` is the mantissa written in base 2 (MSB
691    /// first). The `0xb` literal prefix is intentional: it visually marks the
692    /// value as "binary hex-float", distinct from the C99 `0x<hex>p<exp>`
693    /// format.
694    ///
695    /// Non-finite values display as `NaN`, `inf`, or `-inf`.
696    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
697        // Non-finite values — must come before `is_zero()` check because
698        // NaN and Inf have mantissa=0 and would otherwise display as "0xb0p0".
699        match self.class {
700            FloatClass::Nan => return f.write_str("NaN"),
701            FloatClass::Infinite => {
702                return f.write_str(if self.sign == Sign::Negative {
703                    "-inf"
704                } else {
705                    "inf"
706                });
707            }
708            FloatClass::Finite => {}
709        }
710        // Existing hex-float body for finite values:
711        if self.is_zero() {
712            return f.write_str("0xb0p0");
713        }
714        if self.sign == Sign::Negative {
715            f.write_str("-")?;
716        }
717        f.write_str("0xb")?;
718        let bits = self.mantissa.bit_length();
719        // Write MSB-first.
720        for i in (0..bits).rev() {
721            if self.mantissa.test_bit(i) {
722                f.write_str("1")?;
723            } else {
724                f.write_str("0")?;
725            }
726        }
727        write!(f, "p{}", self.exponent)
728    }
729}
730
731impl fmt::Debug for BigFloat {
732    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
733        write!(
734            f,
735            "BigFloat {{ class: {:?}, sign: {:?}, mantissa: {}, exponent: {}, precision: {} }}",
736            self.class, self.sign, self.mantissa, self.exponent, self.precision
737        )
738    }
739}