Skip to main content

zerodds_float128/
float.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! The value itself: the bit pattern, the fields, and the constants.
4
5/// Bits in the stored fraction. IEEE 754-2008 clause 3.6, binary128.
6pub(crate) const FRACTION_BITS: u32 = 112;
7/// Bits in the biased exponent field.
8pub(crate) const EXPONENT_BITS: u32 = 15;
9/// What is added to the unbiased exponent to get the stored one.
10pub(crate) const EXPONENT_BIAS: i32 = 16383;
11/// The stored exponent of an infinity or a NaN — all ones.
12pub(crate) const EXPONENT_MAX: u32 = (1 << EXPONENT_BITS) - 1;
13/// The stored fraction, masked off.
14pub(crate) const FRACTION_MASK: u128 = (1u128 << FRACTION_BITS) - 1;
15/// The sign bit.
16pub(crate) const SIGN_MASK: u128 = 1u128 << (FRACTION_BITS + EXPONENT_BITS);
17/// Where the exponent field starts.
18pub(crate) const EXPONENT_SHIFT: u32 = FRACTION_BITS;
19/// Significand bits including the implicit one: what "precision" means.
20pub(crate) const SIGNIFICAND_BITS: u32 = FRACTION_BITS + 1;
21/// The bit that makes a NaN quiet, per IEEE 754-2008 clause 6.2.1.
22pub(crate) const QUIET_BIT: u128 = 1u128 << (FRACTION_BITS - 1);
23
24/// What a value is, in the sense of IEEE 754-2008 clause 5.7.2.
25///
26/// The same five classes `core::num::FpCategory` names, spelled here so
27/// that a `no_std` caller matching on them does not have to reach into
28/// `core::num` for a type this crate could have owned.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30pub enum Class {
31    /// A quiet or signalling NaN.
32    Nan,
33    /// Positive or negative infinity.
34    Infinite,
35    /// A finite value with the implicit leading one.
36    Normal,
37    /// A finite value below `MIN_POSITIVE`, with no leading one.
38    Subnormal,
39    /// Positive or negative zero.
40    Zero,
41}
42
43/// An IEEE 754-2008 binary128 value.
44///
45/// Held as the 128-bit pattern, because that is what goes on the wire
46/// and every operation here is defined on it. `to_be_bytes` and
47/// `from_be_bytes` are the wire form: CORBA Figure 9.2 shows the whole
48/// sixteen bytes reversed between the two orders, not two eight-byte
49/// halves swapped independently, and RTI Connext 7.7 was measured doing
50/// exactly that (`docs/float128-column.md` §2).
51///
52/// # Equality
53///
54/// `PartialEq` is IEEE equality, the same as `f64`: `-0.0 == 0.0`, and a
55/// NaN equals nothing including itself. So there is no `Eq`, no `Ord`
56/// and no `Hash` — a key holder wants the *bit pattern*, which
57/// [`Float128::to_bits`] and [`Float128::to_be_bytes`] give, and a total
58/// order is [`Float128::total_cmp`]. Deriving `Eq` on IEEE equality
59/// would make `-0.0` and `0.0` interchangeable in a hash map, which is
60/// how a key holder loses an instance.
61#[derive(Debug, Clone, Copy, Default)]
62pub struct Float128 {
63    bits: u128,
64}
65
66impl Float128 {
67    /// Positive zero.
68    pub const ZERO: Self = Self { bits: 0 };
69    /// Negative zero. Distinct from [`Self::ZERO`] on the wire, equal to
70    /// it under `==`, ordered below it by [`Self::total_cmp`].
71    pub const NEG_ZERO: Self = Self { bits: SIGN_MASK };
72    /// One.
73    pub const ONE: Self = Self {
74        bits: (EXPONENT_BIAS as u128) << EXPONENT_SHIFT,
75    };
76    /// Minus one.
77    pub const NEG_ONE: Self = Self {
78        bits: SIGN_MASK | ((EXPONENT_BIAS as u128) << EXPONENT_SHIFT),
79    };
80    /// Positive infinity.
81    pub const INFINITY: Self = Self {
82        bits: (EXPONENT_MAX as u128) << EXPONENT_SHIFT,
83    };
84    /// Negative infinity.
85    pub const NEG_INFINITY: Self = Self {
86        bits: SIGN_MASK | ((EXPONENT_MAX as u128) << EXPONENT_SHIFT),
87    };
88    /// A quiet NaN with no payload.
89    pub const NAN: Self = Self {
90        bits: ((EXPONENT_MAX as u128) << EXPONENT_SHIFT) | QUIET_BIT,
91    };
92    /// The largest finite value, `(2 - 2^-112) * 2^16383`.
93    pub const MAX: Self = Self {
94        bits: (((EXPONENT_MAX - 1) as u128) << EXPONENT_SHIFT) | FRACTION_MASK,
95    };
96    /// The most negative finite value.
97    pub const MIN: Self = Self {
98        bits: SIGN_MASK | (((EXPONENT_MAX - 1) as u128) << EXPONENT_SHIFT) | FRACTION_MASK,
99    };
100    /// The smallest positive *normal* value, `2^-16382`.
101    pub const MIN_POSITIVE: Self = Self {
102        bits: 1u128 << EXPONENT_SHIFT,
103    };
104    /// The smallest positive value of any kind, `2^-16494`.
105    pub const MIN_SUBNORMAL: Self = Self { bits: 1 };
106    /// The step from `1.0` to the next larger value, `2^-112`.
107    pub const EPSILON: Self = Self {
108        bits: ((EXPONENT_BIAS - FRACTION_BITS as i32) as u128) << EXPONENT_SHIFT,
109    };
110
111    /// Significand bits including the implicit leading one: 113.
112    pub const MANTISSA_DIGITS: u32 = SIGNIFICAND_BITS;
113    /// Decimal digits that always round-trip: `ceil(113 * log10 2) + 1`.
114    ///
115    /// Thirty-six is the number [`Float128::to_shortest_decimal`] never
116    /// exceeds and the number a peer needs to read one back exactly.
117    pub const DIGITS_ROUND_TRIP: usize = 36;
118
119    /// From the raw 128-bit pattern.
120    #[must_use]
121    pub const fn from_bits(bits: u128) -> Self {
122        Self { bits }
123    }
124
125    /// The raw 128-bit pattern.
126    #[must_use]
127    pub const fn to_bits(self) -> u128 {
128        self.bits
129    }
130
131    /// From sixteen big-endian bytes — the CDR big-endian wire form.
132    #[must_use]
133    pub const fn from_be_bytes(bytes: [u8; 16]) -> Self {
134        Self {
135            bits: u128::from_be_bytes(bytes),
136        }
137    }
138
139    /// Sixteen big-endian bytes.
140    #[must_use]
141    pub const fn to_be_bytes(self) -> [u8; 16] {
142        self.bits.to_be_bytes()
143    }
144
145    /// From sixteen little-endian bytes — the CDR little-endian wire
146    /// form, which is the big-endian one reversed as a whole.
147    #[must_use]
148    pub const fn from_le_bytes(bytes: [u8; 16]) -> Self {
149        Self {
150            bits: u128::from_le_bytes(bytes),
151        }
152    }
153
154    /// Sixteen little-endian bytes.
155    #[must_use]
156    pub const fn to_le_bytes(self) -> [u8; 16] {
157        self.bits.to_le_bytes()
158    }
159
160    // -- fields ----------------------------------------------------------
161
162    /// The sign bit, as it is stored. True for negative, including
163    /// `-0.0` and a negative NaN.
164    #[must_use]
165    pub const fn is_sign_negative(self) -> bool {
166        self.bits & SIGN_MASK != 0
167    }
168
169    /// The complement of [`Self::is_sign_negative`].
170    #[must_use]
171    pub const fn is_sign_positive(self) -> bool {
172        !self.is_sign_negative()
173    }
174
175    /// The biased exponent field, `0..=32767`.
176    #[must_use]
177    pub(crate) const fn biased_exponent(self) -> u32 {
178        ((self.bits >> EXPONENT_SHIFT) & (EXPONENT_MAX as u128)) as u32
179    }
180
181    /// The stored fraction, without the implicit leading one.
182    #[must_use]
183    pub(crate) const fn fraction(self) -> u128 {
184        self.bits & FRACTION_MASK
185    }
186
187    /// What kind of value this is.
188    #[must_use]
189    pub const fn classify(self) -> Class {
190        let exponent = self.biased_exponent();
191        let fraction = self.fraction();
192        if exponent == EXPONENT_MAX {
193            if fraction == 0 {
194                Class::Infinite
195            } else {
196                Class::Nan
197            }
198        } else if exponent == 0 {
199            if fraction == 0 {
200                Class::Zero
201            } else {
202                Class::Subnormal
203            }
204        } else {
205            Class::Normal
206        }
207    }
208
209    /// Whether this is a NaN.
210    #[must_use]
211    pub const fn is_nan(self) -> bool {
212        matches!(self.classify(), Class::Nan)
213    }
214
215    /// Whether this is an infinity.
216    #[must_use]
217    pub const fn is_infinite(self) -> bool {
218        matches!(self.classify(), Class::Infinite)
219    }
220
221    /// Whether this is neither infinite nor a NaN.
222    #[must_use]
223    pub const fn is_finite(self) -> bool {
224        self.biased_exponent() != EXPONENT_MAX
225    }
226
227    /// Whether this is `+0.0` or `-0.0`.
228    #[must_use]
229    pub const fn is_zero(self) -> bool {
230        self.bits & !SIGN_MASK == 0
231    }
232
233    /// Whether this is finite, non-zero and below [`Self::MIN_POSITIVE`].
234    #[must_use]
235    pub const fn is_subnormal(self) -> bool {
236        matches!(self.classify(), Class::Subnormal)
237    }
238
239    /// Whether this is finite and normal — not zero, not subnormal.
240    #[must_use]
241    pub const fn is_normal(self) -> bool {
242        matches!(self.classify(), Class::Normal)
243    }
244
245    /// Whether this NaN is quiet. False for anything that is not a NaN.
246    #[must_use]
247    pub const fn is_quiet_nan(self) -> bool {
248        self.is_nan() && (self.bits & QUIET_BIT) != 0
249    }
250
251    // -- sign operations, which need no arithmetic -----------------------
252
253    /// The same magnitude with the sign bit cleared. NaN payloads and
254    /// signalling-ness survive, per IEEE 754-2008 clause 5.5.1.
255    #[must_use]
256    pub const fn abs(self) -> Self {
257        Self {
258            bits: self.bits & !SIGN_MASK,
259        }
260    }
261
262    /// The same magnitude with the sign bit flipped.
263    #[must_use]
264    pub const fn neg(self) -> Self {
265        Self {
266            bits: self.bits ^ SIGN_MASK,
267        }
268    }
269
270    /// This magnitude with `sign`'s sign bit.
271    #[must_use]
272    pub const fn copysign(self, sign: Self) -> Self {
273        Self {
274            bits: (self.bits & !SIGN_MASK) | (sign.bits & SIGN_MASK),
275        }
276    }
277
278    // -- decomposition ---------------------------------------------------
279
280    /// The value as `(negative, significand, exponent)` with
281    /// `value = (-1)^negative * significand * 2^exponent`.
282    ///
283    /// `None` for an infinity or a NaN, which no such triple describes.
284    /// The significand is the *integral* one — the implicit leading bit
285    /// is materialised for a normal value — so the triple is exact and
286    /// needs no separate "is subnormal" flag to interpret.
287    #[must_use]
288    pub const fn decompose(self) -> Option<(bool, u128, i32)> {
289        if !self.is_finite() {
290            return None;
291        }
292        let biased = self.biased_exponent();
293        let fraction = self.fraction();
294        let (significand, exponent) = if biased == 0 {
295            // Subnormal and zero share the exponent of the smallest
296            // normal, which is what makes the two encodings continuous.
297            (fraction, 1 - EXPONENT_BIAS - FRACTION_BITS as i32)
298        } else {
299            (
300                fraction | (1u128 << FRACTION_BITS),
301                biased as i32 - EXPONENT_BIAS - FRACTION_BITS as i32,
302            )
303        };
304        Some((self.is_sign_negative(), significand, exponent))
305    }
306
307    /// The inverse of [`Self::decompose`], rounding to nearest with ties
308    /// to even and reporting whether that rounding lost anything.
309    ///
310    /// This is the one place where a value that does not fit is
311    /// *constructed* rather than refused, because every conversion into
312    /// binary128 funnels through it and each caller decides for itself
313    /// what an inexact result means. Overflow gives an infinity, which
314    /// is what IEEE 754-2008 clause 7.4 requires of the default rounding
315    /// direction, and it is reported inexact so no caller mistakes it
316    /// for a value that was sent.
317    pub(crate) fn from_parts_rounded(
318        negative: bool,
319        significand: u128,
320        exponent: i32,
321    ) -> (Self, bool) {
322        if significand == 0 {
323            return (if negative { Self::NEG_ZERO } else { Self::ZERO }, false);
324        }
325        // Clamped before anything is added to it. A decimal parser can
326        // hand this an exponent of a hundred thousand, and the answer
327        // there is a saturated infinity or zero, not a wrapped `i32`
328        // that lands back inside the normal range.
329        const OVERFLOW: i32 = EXPONENT_MAX as i32;
330        const UNDERFLOW: i32 = -(SIGNIFICAND_BITS as i32) - EXPONENT_BIAS - 8;
331        if exponent > OVERFLOW {
332            let value = if negative {
333                Self::NEG_INFINITY
334            } else {
335                Self::INFINITY
336            };
337            return (value, true);
338        }
339        if exponent < UNDERFLOW - 128 {
340            let value = if negative { Self::NEG_ZERO } else { Self::ZERO };
341            return (value, true);
342        }
343
344        // Normalise so the leading one sits at bit `FRACTION_BITS`.
345        let leading = 127 - significand.leading_zeros();
346        let mut exponent = exponent;
347        let mut significand = significand;
348        let mut sticky = false;
349        if leading > FRACTION_BITS {
350            let drop = leading - FRACTION_BITS;
351            let (rounded, lost) = round_shift_right(significand, drop);
352            significand = rounded;
353            sticky = lost;
354            exponent += drop as i32;
355            // Rounding up can carry into the next binade.
356            if significand > (FRACTION_MASK | (1u128 << FRACTION_BITS)) {
357                significand >>= 1;
358                exponent += 1;
359            }
360        } else {
361            let shift = FRACTION_BITS - leading;
362            significand <<= shift;
363            exponent -= shift as i32;
364        }
365
366        // `exponent` now scales an integral significand whose leading
367        // one is at bit 112, so the stored exponent is offset by that.
368        let biased = exponent + EXPONENT_BIAS + FRACTION_BITS as i32;
369        if biased >= EXPONENT_MAX as i32 {
370            let value = if negative {
371                Self::NEG_INFINITY
372            } else {
373                Self::INFINITY
374            };
375            return (value, true);
376        }
377        if biased <= 0 {
378            // Subnormal: shift the significand down into the fixed
379            // exponent-zero scale, rounding again. The shift can exceed
380            // the significand's width, and `round_shift_right` still
381            // answers correctly there — a value just over half the
382            // smallest subnormal rounds up to it rather than to zero.
383            // `biased` is at most zero here, so `1 - biased` is `1 + |biased|`
384            // and the conversion is total by construction — no fallback, and
385            // so no value a fallback could be wrong about.
386            let drop = 1 + biased.unsigned_abs();
387            let (rounded, lost) = round_shift_right(significand, drop);
388            sticky |= lost;
389            // Rounding a subnormal up can reach the smallest normal,
390            // whose encoding is exactly `1 << FRACTION_BITS` — the carry
391            // out of the fraction lands in the exponent field on its
392            // own, which is the point of the biased-exponent layout, so
393            // the fraction is deliberately not masked here.
394            let bits = (u128::from(negative) << (FRACTION_BITS + EXPONENT_BITS)) | rounded;
395            return (Self { bits }, sticky);
396        }
397
398        let bits = (u128::from(negative) << (FRACTION_BITS + EXPONENT_BITS))
399            | ((biased as u128) << EXPONENT_SHIFT)
400            | (significand & FRACTION_MASK);
401        (Self { bits }, sticky)
402    }
403}
404
405/// Shift right by `shift`, rounding to nearest with ties to even.
406///
407/// Returns the rounded value and whether anything was discarded. A
408/// caller that only looked at the value would report a truncation as
409/// exact, which is the class of defect this crate exists to remove.
410pub(crate) fn round_shift_right(value: u128, shift: u32) -> (u128, bool) {
411    if shift == 0 {
412        return (value, false);
413    }
414    if shift >= 128 {
415        return (0, value != 0);
416    }
417    let kept = value >> shift;
418    let dropped = value & ((1u128 << shift) - 1);
419    if dropped == 0 {
420        return (kept, false);
421    }
422    let half = 1u128 << (shift - 1);
423    let rounded = if dropped > half || (dropped == half && kept & 1 == 1) {
424        kept + 1
425    } else {
426        kept
427    };
428    (rounded, true)
429}
430
431impl core::ops::Neg for Float128 {
432    type Output = Self;
433
434    fn neg(self) -> Self {
435        Self::neg(self)
436    }
437}