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 = "ient + &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}