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}