Skip to main content

machina_softfloat/
parts.rs

1// SPDX-License-Identifier: MIT
2// Decomposed floating-point representation and pack/unpack logic.
3//
4// Internal convention:
5//   frac is a u128 with the integer bit at position 126.
6//   For normal numbers: bit 126 = 1 (the implicit/explicit
7//   leading one), bits [125 .. 126-FRAC_BITS] hold the
8//   explicit fraction, lower bits are zero after unpack
9//   (used as guard/round/sticky during operations).
10//
11// The exponent `exp` is *unbiased*: for a normal number with
12// biased exponent `e`, exp = e - BIAS.  Subnormals are
13// normalized on unpack so exp = 1 - BIAS - shift_amount.
14
15use crate::env::{ExcFlags, FloatEnv, RoundMode, Tininess};
16use crate::types::{BitOps, FloatFormat};
17
18/// Integer bit position inside frac (u128).
19const INT_BIT: u32 = 126;
20
21// ---------------------------------------------------------------
22// FloatClass
23// ---------------------------------------------------------------
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum FloatClass {
27    Normal,
28    Zero,
29    Inf,
30    QNaN,
31    SNaN,
32}
33
34// ---------------------------------------------------------------
35// FloatParts
36// ---------------------------------------------------------------
37
38#[derive(Clone, Copy, Debug)]
39pub struct FloatParts {
40    pub sign: bool,
41    pub exp: i32,
42    pub frac: u128,
43    pub cls: FloatClass,
44}
45
46impl FloatParts {
47    pub fn is_nan(&self) -> bool {
48        matches!(self.cls, FloatClass::QNaN | FloatClass::SNaN)
49    }
50
51    pub fn is_inf(&self) -> bool {
52        self.cls == FloatClass::Inf
53    }
54
55    /// Canonical quiet NaN for format F.
56    pub fn default_nan<F: FloatFormat>() -> Self {
57        // Canonical NaN: positive, quiet, MSB of fraction set.
58        Self {
59            sign: false,
60            exp: 0,
61            frac: 1u128 << (INT_BIT - 1), // quiet bit
62            cls: FloatClass::QNaN,
63        }
64    }
65}
66
67// ---------------------------------------------------------------
68// Unpack
69// ---------------------------------------------------------------
70
71pub fn unpack<F: FloatFormat>(val: F) -> FloatParts {
72    let bits = val.to_bits().to_u128();
73    let total_bits = 1
74        + F::EXP_BITS
75        + F::FRAC_BITS
76        + if F::HAS_EXPLICIT_INT { 1 } else { 0 };
77    let _ = total_bits; // informational
78
79    // Fraction mask (includes explicit integer bit for x80)
80    let frac_total = if F::HAS_EXPLICIT_INT {
81        F::FRAC_BITS + 1
82    } else {
83        F::FRAC_BITS
84    };
85    let frac_mask = (1u128 << frac_total) - 1;
86    let raw_frac = bits & frac_mask;
87
88    let exp_mask = (1u128 << F::EXP_BITS) - 1;
89    let raw_exp = ((bits >> frac_total) & exp_mask) as u32;
90
91    let sign_shift = frac_total + F::EXP_BITS;
92    let sign = ((bits >> sign_shift) & 1) != 0;
93
94    let max_exp = (1u32 << F::EXP_BITS) - 1;
95
96    if raw_exp == max_exp {
97        // Infinity or NaN
98        let frac_for_nan = if F::HAS_EXPLICIT_INT {
99            // For x80, ignore the integer bit for
100            // inf/nan classification of the fraction part.
101            raw_frac & ((1u128 << F::FRAC_BITS) - 1)
102        } else {
103            raw_frac
104        };
105
106        if frac_for_nan == 0 {
107            // Check for x80 pseudo-infinity (integer bit
108            // clear): treat as NaN.
109            if F::HAS_EXPLICIT_INT && (raw_frac >> F::FRAC_BITS) & 1 == 0 {
110                return FloatParts {
111                    sign,
112                    exp: 0,
113                    frac: 1u128 << (INT_BIT - 1),
114                    cls: FloatClass::QNaN,
115                };
116            }
117            return FloatParts {
118                sign,
119                exp: 0,
120                frac: 0,
121                cls: FloatClass::Inf,
122            };
123        }
124
125        // NaN: quiet bit is the MSB of the fraction field.
126        let quiet_bit = F::FRAC_BITS - 1;
127        let is_quiet = (raw_frac >> quiet_bit) & 1 != 0;
128
129        // Shift fraction into internal position.
130        // For NaN payload we store the raw fraction
131        // (without integer bit for x80) left-aligned
132        // below INT_BIT.
133        let payload = if F::HAS_EXPLICIT_INT {
134            raw_frac & ((1u128 << F::FRAC_BITS) - 1)
135        } else {
136            raw_frac
137        };
138        let frac = payload << (INT_BIT - F::FRAC_BITS);
139
140        let cls = if is_quiet {
141            FloatClass::QNaN
142        } else {
143            FloatClass::SNaN
144        };
145        return FloatParts {
146            sign,
147            exp: 0,
148            frac,
149            cls,
150        };
151    }
152
153    if raw_exp == 0 {
154        if raw_frac == 0 {
155            return FloatParts {
156                sign,
157                exp: 0,
158                frac: 0,
159                cls: FloatClass::Zero,
160            };
161        }
162        // Subnormal: normalize
163        return unpack_subnormal::<F>(sign, raw_frac);
164    }
165
166    // Normal
167    let exp = raw_exp as i32 - F::BIAS;
168    let frac = if F::HAS_EXPLICIT_INT {
169        // raw_frac includes the explicit integer bit at
170        // position FRAC_BITS. Shift so that the integer bit
171        // lands at INT_BIT.
172        raw_frac << (INT_BIT - F::FRAC_BITS)
173    } else {
174        (1u128 << INT_BIT) | (raw_frac << (INT_BIT - F::FRAC_BITS))
175    };
176
177    FloatParts {
178        sign,
179        exp,
180        frac,
181        cls: FloatClass::Normal,
182    }
183}
184
185fn unpack_subnormal<F: FloatFormat>(sign: bool, raw_frac: u128) -> FloatParts {
186    // Minimum exponent for the format (exponent of subnormals
187    // if they had biased_exp == 1).
188    let exp_min = 1 - F::BIAS;
189
190    // Place the fraction bits left-aligned just below INT_BIT.
191    let shift_base = INT_BIT - F::FRAC_BITS;
192    let mut frac = raw_frac << shift_base;
193
194    // Normalize: shift left until bit INT_BIT is set.
195    let lz = frac.leading_zeros();
196    // frac is u128, INT_BIT = 126, so we need
197    // (127 - INT_BIT) = 1 leading zero when bit 126 is set.
198    let shift = lz - (127 - INT_BIT);
199    frac <<= shift;
200    let exp = exp_min - shift as i32;
201
202    FloatParts {
203        sign,
204        exp,
205        frac,
206        cls: FloatClass::Normal,
207    }
208}
209
210// ---------------------------------------------------------------
211// Pack
212// ---------------------------------------------------------------
213
214/// Round, handle overflow/underflow, and pack into target format.
215pub fn round_pack<F: FloatFormat>(
216    parts: &mut FloatParts,
217    env: &mut FloatEnv,
218) -> F {
219    match parts.cls {
220        FloatClass::Zero => return pack_zero::<F>(parts.sign),
221        FloatClass::Inf => return pack_inf::<F>(parts.sign),
222        FloatClass::QNaN | FloatClass::SNaN => {
223            return pack_nan::<F>(parts, env);
224        }
225        FloatClass::Normal => {}
226    }
227
228    // Normalize: ensure integer bit is at INT_BIT.
229    if parts.frac == 0 {
230        return pack_zero::<F>(parts.sign);
231    }
232    let lz = parts.frac.leading_zeros();
233    let target_lz = 127 - INT_BIT; // = 1
234    if lz > target_lz {
235        let shift = lz - target_lz;
236        parts.frac <<= shift;
237        parts.exp -= shift as i32;
238    } else if lz < target_lz {
239        let shift = target_lz - lz;
240        // Shift right, preserving sticky bits.
241        let sticky = if parts.frac & ((1u128 << shift) - 1) != 0 {
242            1u128
243        } else {
244            0
245        };
246        parts.frac = (parts.frac >> shift) | sticky;
247        parts.exp += shift as i32;
248    }
249
250    pack::<F>(parts, env)
251}
252
253/// Pack an already-normalized FloatParts into format F.
254/// The integer bit must be at position INT_BIT.
255pub fn pack<F: FloatFormat>(parts: &mut FloatParts, env: &mut FloatEnv) -> F {
256    match parts.cls {
257        FloatClass::Zero => return pack_zero::<F>(parts.sign),
258        FloatClass::Inf => return pack_inf::<F>(parts.sign),
259        FloatClass::QNaN | FloatClass::SNaN => {
260            return pack_nan::<F>(parts, env);
261        }
262        FloatClass::Normal => {}
263    }
264
265    if parts.frac == 0 {
266        return pack_zero::<F>(parts.sign);
267    }
268
269    let max_exp = ((1u32 << F::EXP_BITS) - 1) as i32;
270    let rm = env.round_mode();
271
272    // Number of extra bits below the fraction field.
273    let round_pos = INT_BIT - F::FRAC_BITS;
274    let round_mask = (1u128 << round_pos) - 1;
275    let half = 1u128 << (round_pos - 1);
276
277    let mut biased_exp = parts.exp + F::BIAS;
278
279    // --- Handle potential underflow (subnormal result) ---
280    if biased_exp < 1 {
281        // Need to right-shift frac to make biased_exp == 0.
282        let shift = (1 - biased_exp) as u32;
283        if shift >= 128 {
284            // Completely shifted out.
285            let nonzero = parts.frac != 0;
286            parts.frac = 0;
287            if nonzero {
288                parts.frac = 1; // sticky
289            }
290        } else {
291            let sticky = if parts.frac & ((1u128 << shift) - 1) != 0 {
292                1u128
293            } else {
294                0
295            };
296            parts.frac = (parts.frac >> shift) | sticky;
297        }
298        biased_exp = 0;
299
300        // Detect tininess
301        let is_tiny_before = true; // biased_exp < 1
302        let remainder = parts.frac & round_mask;
303        if remainder != 0 {
304            // Check tininess mode
305            let is_tiny = match env.tininess() {
306                Tininess::BeforeRounding => is_tiny_before,
307                Tininess::AfterRounding => {
308                    // Tiny if after rounding it's still
309                    // subnormal (integer bit not at
310                    // INT_BIT).
311                    let rounded = apply_rounding(
312                        parts.frac, remainder, half, round_mask, rm, parts.sign,
313                    );
314                    (rounded >> INT_BIT) & 1 == 0
315                }
316            };
317            if is_tiny {
318                env.raise(ExcFlags::UNDERFLOW);
319            }
320        }
321    }
322
323    // --- Rounding ---
324    let remainder = parts.frac & round_mask;
325    let inexact = remainder != 0;
326
327    parts.frac =
328        apply_rounding(parts.frac, remainder, half, round_mask, rm, parts.sign);
329
330    // Check if rounding caused the integer bit to overflow
331    // (e.g., frac was all-ones in the fraction field).
332    if parts.frac >> (INT_BIT + 1) != 0 {
333        parts.frac >>= 1;
334        biased_exp += 1;
335    }
336
337    // --- Overflow check ---
338    if biased_exp >= max_exp {
339        env.raise(ExcFlags::OVERFLOW | ExcFlags::INEXACT);
340        return overflow_result::<F>(parts.sign, rm);
341    }
342
343    if inexact {
344        env.raise(ExcFlags::INEXACT);
345    }
346
347    // --- Assemble the result ---
348    let frac_field = if biased_exp == 0 {
349        // Subnormal: no implicit integer bit.
350        // Extract fraction bits from below INT_BIT.
351        (parts.frac >> round_pos) & ((1u128 << F::FRAC_BITS) - 1)
352    } else if F::HAS_EXPLICIT_INT {
353        // x80: include the integer bit.
354        (parts.frac >> (INT_BIT - F::FRAC_BITS))
355            & ((1u128 << (F::FRAC_BITS + 1)) - 1)
356    } else {
357        // Drop the implicit integer bit at INT_BIT.
358        (parts.frac >> round_pos) & ((1u128 << F::FRAC_BITS) - 1)
359    };
360
361    let frac_total = if F::HAS_EXPLICIT_INT {
362        F::FRAC_BITS + 1
363    } else {
364        F::FRAC_BITS
365    };
366
367    let bits = ((parts.sign as u128) << (frac_total + F::EXP_BITS))
368        | ((biased_exp as u128) << frac_total)
369        | frac_field;
370
371    F::from_bits(<F::Bits as crate::types::BitOps>::from_u128(bits))
372}
373
374// ---------------------------------------------------------------
375// Rounding helper
376// ---------------------------------------------------------------
377
378fn apply_rounding(
379    frac: u128,
380    remainder: u128,
381    half: u128,
382    round_mask: u128,
383    rm: RoundMode,
384    sign: bool,
385) -> u128 {
386    let truncated = frac & !round_mask;
387    let lsb_set = (frac >> round_mask.count_ones()) & 1 != 0;
388
389    match rm {
390        RoundMode::NearEven => {
391            if remainder > half {
392                truncated.wrapping_add(round_mask + 1)
393            } else if remainder == half {
394                // Ties to even: round up if LSB is odd.
395                if lsb_set {
396                    truncated.wrapping_add(round_mask + 1)
397                } else {
398                    truncated
399                }
400            } else {
401                truncated
402            }
403        }
404        RoundMode::NearMaxMag => {
405            // Ties away from zero.
406            if remainder >= half {
407                truncated.wrapping_add(round_mask + 1)
408            } else {
409                truncated
410            }
411        }
412        RoundMode::ToZero => truncated,
413        RoundMode::Down => {
414            // Towards -inf.
415            if sign && remainder != 0 {
416                truncated.wrapping_add(round_mask + 1)
417            } else {
418                truncated
419            }
420        }
421        RoundMode::Up => {
422            // Towards +inf.
423            if !sign && remainder != 0 {
424                truncated.wrapping_add(round_mask + 1)
425            } else {
426                truncated
427            }
428        }
429        RoundMode::Odd => {
430            if remainder != 0 {
431                // Set the LSB to 1.
432                truncated | (round_mask + 1)
433            } else {
434                truncated
435            }
436        }
437    }
438}
439
440// ---------------------------------------------------------------
441// Overflow result
442// ---------------------------------------------------------------
443
444fn overflow_result<F: FloatFormat>(sign: bool, rm: RoundMode) -> F {
445    // Depending on rounding mode, overflow may produce infinity
446    // or the largest finite number.
447    match rm {
448        RoundMode::NearEven | RoundMode::NearMaxMag => pack_inf::<F>(sign),
449        RoundMode::ToZero => pack_max_finite::<F>(sign),
450        RoundMode::Down => {
451            if sign {
452                pack_inf::<F>(true)
453            } else {
454                pack_max_finite::<F>(false)
455            }
456        }
457        RoundMode::Up => {
458            if sign {
459                pack_max_finite::<F>(true)
460            } else {
461                pack_inf::<F>(false)
462            }
463        }
464        RoundMode::Odd => pack_max_finite::<F>(sign),
465    }
466}
467
468// ---------------------------------------------------------------
469// Packing helpers for special values
470// ---------------------------------------------------------------
471
472fn pack_zero<F: FloatFormat>(sign: bool) -> F {
473    let frac_total = if F::HAS_EXPLICIT_INT {
474        F::FRAC_BITS + 1
475    } else {
476        F::FRAC_BITS
477    };
478    let bits = (sign as u128) << (frac_total + F::EXP_BITS);
479    F::from_bits(<F::Bits as crate::types::BitOps>::from_u128(bits))
480}
481
482fn pack_inf<F: FloatFormat>(sign: bool) -> F {
483    let max_exp = (1u128 << F::EXP_BITS) - 1;
484    let frac_total = if F::HAS_EXPLICIT_INT {
485        F::FRAC_BITS + 1
486    } else {
487        F::FRAC_BITS
488    };
489
490    let mut bits = ((sign as u128) << (frac_total + F::EXP_BITS))
491        | (max_exp << frac_total);
492
493    // x80 infinity: integer bit set, fraction zero.
494    if F::HAS_EXPLICIT_INT {
495        bits |= 1u128 << F::FRAC_BITS;
496    }
497
498    F::from_bits(<F::Bits as crate::types::BitOps>::from_u128(bits))
499}
500
501fn pack_max_finite<F: FloatFormat>(sign: bool) -> F {
502    let max_exp = (1u128 << F::EXP_BITS) - 2;
503    let frac_total = if F::HAS_EXPLICIT_INT {
504        F::FRAC_BITS + 1
505    } else {
506        F::FRAC_BITS
507    };
508    let frac_all_ones = (1u128 << frac_total) - 1;
509
510    let bits = ((sign as u128) << (frac_total + F::EXP_BITS))
511        | (max_exp << frac_total)
512        | frac_all_ones;
513
514    F::from_bits(<F::Bits as crate::types::BitOps>::from_u128(bits))
515}
516
517fn pack_nan<F: FloatFormat>(parts: &FloatParts, env: &mut FloatEnv) -> F {
518    if env.default_nan() {
519        let dn = FloatParts::default_nan::<F>();
520        return encode_nan::<F>(&dn);
521    }
522
523    // Quieten SNaN if needed.
524    let mut p = *parts;
525    if p.cls == FloatClass::SNaN {
526        p.cls = FloatClass::QNaN;
527        // Set the quiet bit.
528        p.frac |= 1u128 << (INT_BIT - 1);
529    }
530    encode_nan::<F>(&p)
531}
532
533fn encode_nan<F: FloatFormat>(parts: &FloatParts) -> F {
534    let max_exp = (1u128 << F::EXP_BITS) - 1;
535    let frac_total = if F::HAS_EXPLICIT_INT {
536        F::FRAC_BITS + 1
537    } else {
538        F::FRAC_BITS
539    };
540
541    // Extract fraction payload from internal position.
542    let payload = (parts.frac >> (INT_BIT - F::FRAC_BITS))
543        & ((1u128 << F::FRAC_BITS) - 1);
544
545    // Ensure at least the quiet bit is set for QNaN.
546    let quiet_bit = 1u128 << (F::FRAC_BITS - 1);
547    let payload = if parts.cls == FloatClass::QNaN {
548        payload | quiet_bit
549    } else {
550        payload & !quiet_bit
551    };
552
553    // Ensure NaN payload is non-zero.
554    let payload = if payload == 0 { quiet_bit } else { payload };
555
556    let mut bits = ((parts.sign as u128) << (frac_total + F::EXP_BITS))
557        | (max_exp << frac_total)
558        | payload;
559
560    // x80: set the integer bit for NaN.
561    if F::HAS_EXPLICIT_INT {
562        bits |= 1u128 << F::FRAC_BITS;
563    }
564
565    F::from_bits(<F::Bits as crate::types::BitOps>::from_u128(bits))
566}
567
568// ---------------------------------------------------------------
569// NaN propagation
570// ---------------------------------------------------------------
571
572/// IEEE 754 NaN propagation: prefer first SNaN, then first QNaN.
573/// Signals INVALID if either operand is SNaN.
574pub fn nan_propagate(
575    a: &FloatParts,
576    b: &FloatParts,
577    env: &mut FloatEnv,
578) -> FloatParts {
579    if a.cls == FloatClass::SNaN || b.cls == FloatClass::SNaN {
580        env.raise(ExcFlags::INVALID);
581    }
582
583    if env.default_nan() {
584        // Use arbitrary format; caller will re-encode.
585        // The frac/exp don't matter much -- pack_nan
586        // will produce the canonical NaN.
587        return FloatParts {
588            sign: false,
589            exp: 0,
590            frac: 1u128 << (INT_BIT - 1),
591            cls: FloatClass::QNaN,
592        };
593    }
594
595    // Prefer SNaN (quietened) over QNaN, prefer `a` over `b`.
596    let pick = if a.cls == FloatClass::SNaN {
597        a
598    } else if b.cls == FloatClass::SNaN {
599        b
600    } else if a.cls == FloatClass::QNaN {
601        a
602    } else {
603        b
604    };
605
606    let mut result = *pick;
607    // Quieten SNaN.
608    if result.cls == FloatClass::SNaN {
609        result.cls = FloatClass::QNaN;
610        result.frac |= 1u128 << (INT_BIT - 1);
611    }
612    result
613}
614
615/// Propagate NaN from a single operand (unary ops).
616pub fn nan_propagate_one(a: &FloatParts, env: &mut FloatEnv) -> FloatParts {
617    if a.cls == FloatClass::SNaN {
618        env.raise(ExcFlags::INVALID);
619    }
620
621    if env.default_nan() {
622        return FloatParts {
623            sign: false,
624            exp: 0,
625            frac: 1u128 << (INT_BIT - 1),
626            cls: FloatClass::QNaN,
627        };
628    }
629
630    let mut result = *a;
631    if result.cls == FloatClass::SNaN {
632        result.cls = FloatClass::QNaN;
633        result.frac |= 1u128 << (INT_BIT - 1);
634    }
635    result
636}
637
638// ---------------------------------------------------------------
639// Utility: return invalid-operation default NaN
640// ---------------------------------------------------------------
641
642pub fn return_nan<F: FloatFormat>(env: &mut FloatEnv) -> F {
643    env.raise(ExcFlags::INVALID);
644    let dn = FloatParts::default_nan::<F>();
645    encode_nan::<F>(&dn)
646}