Skip to main content

rustyn64_cpu/
softfloat.rs

1//! Soft-float arithmetic with exact IEEE-754 exception flags (T-13-003).
2//!
3//! # Why this exists
4//!
5//! Rust's `f32`/`f64` operators give a correctly-rounded result and **discard
6//! everything else**: there is no way to ask whether the operation was inexact,
7//! underflowed, or what it would have produced under a directed rounding mode.
8//! The VR4300 reports all of that through `FCSR`, so an emulator built on the
9//! native operators can be bit-exact on values and still wrong on every flag —
10//! which is precisely where accuracy ledger **C-11** left the FPU.
11//!
12//! # Why the cheap version does not work
13//!
14//! The tempting shortcut is to compute in `f64` and compare: if the `f64`
15//! result differs from the widened `f32` result, call it inexact. That is
16//! **right in the normal range and wrong where it matters**:
17//!
18//! - For `MUL.S` it happens to hold — the exact product of two 24-bit
19//!   significands needs at most 48 bits, and `f64` carries 53.
20//! - For `ADD.S` it does not. The exact sum of `2^127` and `2^-149` spans ~277
21//!   significand bits, so the `f64` sum is *itself* rounded and the comparison
22//!   silently becomes a guess.
23//! - For any `.D` operation there is no wider type to compute in at all.
24//!
25//! An earlier attempt along those lines was implemented and reverted (C-10). A
26//! flag that is right in the common case and wrong in the range the oracle
27//! deliberately probes is worse than no flag, because it makes every later
28//! result stop being evidence.
29//!
30//! # How this works instead
31//!
32//! One code path for both formats, parameterized by [`Format`]. Values are
33//! unpacked to `(sign, significand, exponent)` with the significand as a plain
34//! integer — value = `sig × 2^exp` — computed at a widened scale in `u128`, and
35//! rounded **once** at the end by a single internal rounding step, which is the
36//! only place any flag is
37//! produced. Bits that fall off the bottom are never simply dropped: they are
38//! folded into a sticky bit, which is what makes `inexact` exact rather than
39//! approximate.
40//!
41//! There is no `unsafe`, no allocation and no `std`; the widest type used is
42//! `u128`, which `core` provides everywhere this crate builds.
43//!
44//! # What is deliberately NOT modeled here
45//!
46//! The VR4300 does not produce subnormal results: it raises the unmaskable
47//! **unimplemented-operation** cause for subnormal operands and results (unless
48//! `FCSR.FS` is set, which flushes instead). This module implements the *IEEE*
49//! behavior and produces the subnormal, because that separation is what lets
50//! it be checked against an independent oracle — every `f32`/`f64` operation in
51//! Rust. Layering the VR4300's refusal on top is a separate change; doing both
52//! at once would leave the arithmetic with nothing to be tested against.
53
54// Four lints are allowed module-wide, each because the thing it warns about is
55// the thing being modeled or tested:
56//
57//   * `float_cmp` -- this module exists to be bit-exact. Every comparison here
58//     is against an exactly-representable value or a bit pattern, and an
59//     epsilon would defeat the purpose of the differential test.
60//   * `unreadable_literal` -- the rounding vectors are transcribed verbatim
61//     from n64-systemtest. Reformatting them breaks the correspondence with the
62//     oracle they were copied from, which is what makes them checkable.
63//   * `cast_precision_loss` -- the test corpora deliberately build floats from
64//     integers; the loss is how the sample is drawn.
65//   * `many_single_char_names` -- `f` is the format and `a`/`b` the operands
66//     throughout, matching the IEEE-754 text this implements.
67#![allow(
68    clippy::float_cmp,
69    clippy::unreadable_literal,
70    clippy::cast_precision_loss,
71    clippy::many_single_char_names
72)]
73
74use crate::fpu::{Flags, Rounding};
75use serde::{Deserialize, Serialize};
76
77/// The parameters of an IEEE-754 binary interchange format.
78///
79/// Held as data rather than as a type parameter so that one implementation
80/// serves both precisions. A second copy of this logic specialized per format
81/// is exactly how the two diverge.
82#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
83pub struct Format {
84    /// Significand bits **including** the implicit leading one (24 / 53).
85    pub p: u32,
86    /// Total width in bits (32 / 64).
87    pub width: u32,
88    /// Exponent bias (127 / 1023).
89    pub bias: i32,
90}
91
92/// Single precision.
93pub const F32: Format = Format {
94    p: 24,
95    width: 32,
96    bias: 127,
97};
98
99/// Double precision.
100pub const F64: Format = Format {
101    p: 53,
102    width: 64,
103    bias: 1023,
104};
105
106impl Format {
107    /// Stored mantissa bits — one fewer than [`Format::p`], the implicit bit
108    /// not being stored.
109    #[must_use]
110    pub const fn man_bits(self) -> u32 {
111        self.p - 1
112    }
113
114    /// The all-ones exponent field, which encodes infinity and NaN.
115    #[must_use]
116    pub const fn max_biased(self) -> u32 {
117        (1u32 << (self.width - self.p)) - 1
118    }
119
120    /// The exponent of the **least significant bit** of the smallest subnormal
121    /// — `-149` for `f32`, `-1074` for `f64`.
122    ///
123    /// This is the floor the rounding step clamps to, and it is what makes a
124    /// result subnormal rather than merely small.
125    #[must_use]
126    pub const fn min_lsb_exp(self) -> i32 {
127        1 - self.bias - self.man_bits() as i32
128    }
129
130    /// The largest finite value's encoding, magnitude only.
131    #[must_use]
132    pub const fn max_finite(self) -> u64 {
133        ((self.max_biased() as u64 - 1) << self.man_bits()) | ((1u64 << self.man_bits()) - 1)
134    }
135
136    /// Positive infinity, magnitude only.
137    #[must_use]
138    pub const fn infinity(self) -> u64 {
139        (self.max_biased() as u64) << self.man_bits()
140    }
141
142    /// The NaN the VR4300 delivers as the result of an invalid operation.
143    ///
144    /// `0x7FBF_FFFF` / `0x7FF7_FFFF_FFFF_FFFF` — the significand's MSB is
145    /// **clear**, which by IEEE-754:2008 would make the result of every invalid
146    /// operation a *signaling* NaN, absurdly re-trapping on first use.
147    ///
148    /// It is not absurd, because the VR4300 uses the **legacy MIPS
149    /// convention**, where MSB set means signaling. Under its own rules this
150    /// is an ordinary quiet NaN. See `fpu::is_snan_f32` and accuracy ledger
151    /// C-12; this value is the corroboration that the convention really is
152    /// inverted rather than the tests being odd.
153    #[must_use]
154    pub const fn default_nan(self) -> u64 {
155        let man = (1u64 << self.man_bits()) - 1;
156        let quiet = 1u64 << (self.man_bits() - 1);
157        self.infinity() | (man & !quiet)
158    }
159}
160
161/// What an unpacked value is.
162#[derive(Clone, Copy, Debug, Eq, PartialEq)]
163enum Class {
164    /// ±0.
165    Zero,
166    /// A non-zero finite value, normal or subnormal.
167    Finite,
168    /// ±∞.
169    Inf,
170    /// Not a number.
171    Nan,
172}
173
174/// A decoded float: for [`Class::Finite`], the value is `sig × 2^exp`.
175///
176/// The significand is a plain integer with **no** normalization requirement,
177/// which is what lets subnormals and normals take the same code path.
178#[derive(Clone, Copy, Debug)]
179struct Unpacked {
180    sign: bool,
181    class: Class,
182    /// Significand for a finite value; the raw trailing payload for a NaN.
183    sig: u128,
184    exp: i32,
185    /// A NaN with the quiet bit clear.
186    snan: bool,
187}
188
189/// Decode an encoding into [`Unpacked`].
190fn unpack(bits: u64, f: Format) -> Unpacked {
191    let man_bits = f.man_bits();
192    let sign = (bits >> (f.width - 1)) & 1 != 0;
193    let man = u128::from(bits & ((1u64 << man_bits) - 1));
194    let biased = ((bits >> man_bits) & u64::from(f.max_biased())) as u32;
195
196    if biased == 0 {
197        return Unpacked {
198            sign,
199            class: if man == 0 { Class::Zero } else { Class::Finite },
200            sig: man,
201            // A subnormal's exponent is the same as the smallest normal's, and
202            // the leading bit is absent rather than implicit.
203            exp: f.min_lsb_exp(),
204            snan: false,
205        };
206    }
207    if biased == f.max_biased() {
208        let signal_bit = 1u128 << (man_bits - 1);
209        return Unpacked {
210            sign,
211            class: if man == 0 { Class::Inf } else { Class::Nan },
212            sig: man,
213            exp: 0,
214            // The VR4300 uses the LEGACY MIPS convention: significand MSB
215            // **set** means signaling, the opposite of IEEE-754:2008. See
216            // `fpu::is_snan_f32` and accuracy ledger C-12. Naming the constant
217            // `quiet_bit` and then testing it for *signaling* would be a trap
218            // for the next reader, so it is named for the position it occupies.
219            snan: man & signal_bit != 0,
220        };
221    }
222    Unpacked {
223        sign,
224        class: Class::Finite,
225        sig: man | (1u128 << man_bits),
226        exp: biased as i32 - f.bias - man_bits as i32,
227        snan: false,
228    }
229}
230
231/// A computed result: the encoding plus what producing it raised.
232#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
233pub struct Rounded {
234    /// The result's encoding, in the low [`Format::width`] bits.
235    pub bits: u64,
236    /// The IEEE exceptions the operation raised.
237    pub flags: Flags,
238}
239
240/// ±0 with no flags.
241const fn zero(sign: bool, f: Format) -> Rounded {
242    Rounded {
243        bits: (sign as u64) << (f.width - 1),
244        flags: Flags::NONE,
245    }
246}
247
248/// ±∞ with no flags.
249const fn inf(sign: bool, f: Format) -> Rounded {
250    Rounded {
251        bits: ((sign as u64) << (f.width - 1)) | f.infinity(),
252        flags: Flags::NONE,
253    }
254}
255
256/// The default NaN, flagged Invalid.
257const fn invalid(f: Format) -> Rounded {
258    Rounded {
259        bits: f.default_nan(),
260        flags: Flags::INVALID,
261    }
262}
263
264/// Convert a two's-complement integer to `f`, honoring `mode`.
265///
266/// An integer is `sign × |v| × 2^0`, so this is `round_pack` with a zero
267/// exponent and no sticky bit — which is the whole point of routing it here
268/// rather than through a Rust `as` cast. `as` rounds to nearest-even
269/// unconditionally, so `CVT.S.W` of a value needing more than 24 significand
270/// bits ignored `FCSR.RM` entirely: n64-systemtest converts `1234567891` under
271/// round-toward-zero and expects `0x4E93_2C05`, where nearest-even gives
272/// `0x4E93_2C06`.
273///
274/// The result cannot overflow (no integer exceeds the largest finite `f64`, and
275/// an `i64` fits inside `f32`'s range) and cannot be subnormal, so `inexact` is
276/// the only flag it can raise.
277#[must_use]
278pub fn from_int(v: i64, f: Format, mode: Rounding) -> Rounded {
279    if v == 0 {
280        // `+0`, never `-0`: an integer zero is unsigned, and `round_pack`'s
281        // `sig == 0` path would take the sign from an operand that has none.
282        return zero(false, f);
283    }
284    round_pack(v < 0, u128::from(v.unsigned_abs()), 0, false, f, mode)
285}
286
287/// Round `sign × (sig + ε) × 2^exp` into `f`, where `ε ∈ (0, 1)` exactly when
288/// `sticky` — and report every flag the rounding raised.
289///
290/// This is the only place a result is rounded and the only place `inexact`,
291/// `overflow` and `underflow` are decided, so those three cannot disagree with
292/// each other by construction.
293///
294/// # Panics
295///
296/// Never in release. `sig == 0` together with `sticky` would describe a value
297/// smaller than any the callers can produce — each of them keeps at least 64
298/// guard bits, so a discarded bit implies a significand far from zero — and the
299/// debug assertion pins that argument rather than leaving it in a comment.
300fn round_pack(sign: bool, sig: u128, exp: i32, sticky: bool, f: Format, mode: Rounding) -> Rounded {
301    debug_assert!(
302        !(sig == 0 && sticky),
303        "a discarded bit implies a large significand; see the callers' guard argument"
304    );
305    if sig == 0 {
306        return zero(sign, f);
307    }
308
309    let used = 128 - sig.leading_zeros() as i32;
310    // Drop down to `p` bits...
311    let to_p = used - f.p as i32;
312    // ...but never below the smallest subnormal's LSB, which is what turns an
313    // out-of-range result into a subnormal instead of a wrong normal.
314    let to_min = f.min_lsb_exp() - exp;
315    let shift = if to_p > to_min { to_p } else { to_min };
316    // **Tininess is detected BEFORE rounding.** IEEE 754 permits either, and the
317    // VR4300 picks "before": `to_min` winning means the exact result sits below
318    // the smallest normal, whatever rounding then does to it.
319    //
320    // This matters exactly when a directed rounding mode pushes a tiny result
321    // back up to a normal one. `FLT_MIN / 1.0000001` under round-toward-+inf
322    // yields `FLT_MIN` again — a perfectly normal number — and hardware still
323    // raises underflow, because the value it rounded *from* was tiny. Deciding
324    // underflow from the packed result instead loses precisely this case, and
325    // n64-systemtest's `DIV.S` set contains it in both signs.
326    let tiny = to_min > to_p;
327
328    let (mut kept, mut exp_f, inexact) = if shift <= 0 {
329        // Room to spare: shifting LEFT is exact.
330        #[allow(clippy::cast_sign_loss)] // guarded by `shift <= 0`
331        let left = (-shift) as u32;
332        (sig << left, exp + shift, sticky)
333    } else {
334        #[allow(clippy::cast_sign_loss)] // guarded by the `else`
335        let sh = shift as u32;
336        // A shift of 128 or more is well-defined here only because everything
337        // is then discarded; `u128 >> 128` is UB-adjacent in other languages
338        // and a panic in Rust, so it is branched rather than relied upon.
339        let (kept, discarded_nonzero) = if sh >= 128 {
340            (0u128, sig != 0)
341        } else {
342            (sig >> sh, sig & ((1u128 << sh) - 1) != 0)
343        };
344        let round_bit = if sh > 128 {
345            false
346        } else {
347            sig >> (sh - 1) & 1 != 0
348        };
349        let lower = if sh <= 1 {
350            false
351        } else if sh > 128 {
352            sig != 0
353        } else {
354            sig & ((1u128 << (sh - 1)) - 1) != 0
355        };
356        let sticky_all = lower || sticky;
357        let inexact = discarded_nonzero || sticky;
358
359        let increment = match mode {
360            // Ties to even: step up on a tie only when it would leave an odd
361            // last bit behind.
362            Rounding::Nearest => round_bit && (sticky_all || kept & 1 != 0),
363            Rounding::TowardZero => false,
364            Rounding::TowardPlusInf => inexact && !sign,
365            Rounding::TowardMinusInf => inexact && sign,
366        };
367        (kept + u128::from(increment), exp + shift, inexact)
368    };
369
370    // A round-up can carry out of the significand: 0x1FF…F + 1 = 0x200…0.
371    if kept >> f.p != 0 {
372        kept >>= 1;
373        exp_f += 1;
374    }
375
376    let mut flags = Flags::NONE;
377    flags.inexact = inexact;
378
379    flags.underflow = tiny && inexact;
380
381    if kept == 0 {
382        // Rounded all the way to zero, which is as tiny as it gets.
383        debug_assert!(tiny || !inexact);
384        return Rounded {
385            bits: (sign as u64) << (f.width - 1),
386            flags,
387        };
388    }
389
390    let sign_bit = (sign as u64) << (f.width - 1);
391    if kept >> (f.p - 1) != 0 {
392        // Normal — the implicit bit is present.
393        let biased = exp_f + f.p as i32 - 1 + f.bias;
394        if biased >= f.max_biased() as i32 {
395            return overflowed(sign, f, mode);
396        }
397        #[allow(clippy::cast_sign_loss)] // `biased >= 1` on this branch
398        let biased = biased as u64;
399        let man = (kept as u64) & ((1u64 << f.man_bits()) - 1);
400        return Rounded {
401            bits: sign_bit | (biased << f.man_bits()) | man,
402            flags,
403        };
404    }
405
406    // Subnormal. `to_min` won the shift, so `exp_f` is the minimum LSB exponent
407    // and `kept` is the stored mantissa verbatim.
408    debug_assert_eq!(exp_f, f.min_lsb_exp());
409    Rounded {
410        bits: sign_bit | (kept as u64),
411        flags,
412    }
413}
414
415/// The result of a magnitude too large for the format.
416///
417/// **Which value comes back depends on the rounding mode**, and it is not
418/// always infinity: a directed mode that rounds *toward* the finite range
419/// delivers the largest finite value instead. Reaching for infinity
420/// unconditionally is correct only for round-to-nearest.
421fn overflowed(sign: bool, f: Format, mode: Rounding) -> Rounded {
422    let sign_bit = (sign as u64) << (f.width - 1);
423    let to_inf = match mode {
424        Rounding::Nearest => true,
425        Rounding::TowardZero => false,
426        Rounding::TowardPlusInf => !sign,
427        Rounding::TowardMinusInf => sign,
428    };
429    let mut flags = Flags::NONE;
430    flags.overflow = true;
431    flags.inexact = true;
432    Rounded {
433        bits: sign_bit | if to_inf { f.infinity() } else { f.max_finite() },
434        flags,
435    }
436}
437
438/// Propagate a NaN operand, or produce the default NaN.
439///
440/// A **signaling** operand raises Invalid; a quiet one does not. Either way
441/// the VR4300 delivers its own default NaN rather than the operand's payload,
442/// which is why nothing is copied through.
443fn nan_result(a: Unpacked, b: Unpacked, f: Format) -> Rounded {
444    let mut flags = Flags::NONE;
445    flags.invalid = a.snan || b.snan;
446    Rounded {
447        bits: f.default_nan(),
448        flags,
449    }
450}
451
452/// Guard bits kept below the larger operand's LSB during an addition.
453///
454/// 64 is far more than correct rounding needs (two guard bits and a sticky
455/// suffice). The margin buys the argument that makes `round_pack`'s debug
456/// assertion hold: bits are discarded only when the exponents differ by more
457/// than this, and at that separation the operands cannot cancel — so a
458/// discarded bit never coexists with a zero significand.
459const GUARD: i32 = 64;
460
461/// `a + b`.
462#[must_use]
463pub fn add(a_bits: u64, b_bits: u64, f: Format, mode: Rounding) -> Rounded {
464    add_unpacked(unpack(a_bits, f), unpack(b_bits, f), f, mode)
465}
466
467/// `a - b` — addition with the subtrahend's sign flipped, which is exact and
468/// is how the hardware does it too.
469#[must_use]
470pub fn sub(a_bits: u64, b_bits: u64, f: Format, mode: Rounding) -> Rounded {
471    let mut b = unpack(b_bits, f);
472    // Flipping the sign of a NaN must not change that it is a NaN, and the
473    // sign of a NaN is not otherwise consulted.
474    b.sign = !b.sign;
475    add_unpacked(unpack(a_bits, f), b, f, mode)
476}
477
478fn add_unpacked(a: Unpacked, b: Unpacked, f: Format, mode: Rounding) -> Rounded {
479    if a.class == Class::Nan || b.class == Class::Nan {
480        return nan_result(a, b, f);
481    }
482    if a.class == Class::Inf || b.class == Class::Inf {
483        return match (a.class, b.class) {
484            // ∞ + (−∞) is the undefined form; same-signed infinities are not.
485            (Class::Inf, Class::Inf) if a.sign != b.sign => invalid(f),
486            (Class::Inf, _) => inf(a.sign, f),
487            _ => inf(b.sign, f),
488        };
489    }
490    if a.class == Class::Zero && b.class == Class::Zero {
491        // (+0) + (−0) is +0 in every mode except toward −∞, where it is −0.
492        // Getting this wrong is invisible until something reads the sign bit.
493        let sign = if a.sign == b.sign {
494            a.sign
495        } else {
496            matches!(mode, Rounding::TowardMinusInf)
497        };
498        return zero(sign, f);
499    }
500    if a.class == Class::Zero {
501        return round_pack(b.sign, b.sig, b.exp, false, f, mode);
502    }
503    if b.class == Class::Zero {
504        return round_pack(a.sign, a.sig, a.exp, false, f, mode);
505    }
506
507    let (hi, lo) = if a.exp >= b.exp { (a, b) } else { (b, a) };
508    let diff = hi.exp - lo.exp;
509    let target_exp = hi.exp - GUARD;
510
511    let hi_scaled = hi.sig << GUARD;
512    #[allow(clippy::cast_sign_loss)] // both branches are guarded on `diff`
513    let (lo_scaled, sticky) = if diff <= GUARD {
514        (lo.sig << (GUARD - diff) as u32, false)
515    } else {
516        let sh = (diff - GUARD) as u32;
517        if sh >= 128 {
518            (0u128, true)
519        } else {
520            (lo.sig >> sh, lo.sig & ((1u128 << sh) - 1) != 0)
521        }
522    };
523
524    if hi.sign == lo.sign {
525        // Same sign: magnitudes add, and any discarded bits stay below.
526        return round_pack(hi.sign, hi_scaled + lo_scaled, target_exp, sticky, f, mode);
527    }
528
529    // Opposite signs: magnitudes subtract.
530    if sticky {
531        // The true subtrahend is `lo_scaled + ε`, so the difference is
532        // `(hi - lo - 1) + (1 - ε)`: one less, with a fresh sticky remainder.
533        // Simply ignoring ε here rounds the wrong way on a tie.
534        return round_pack(
535            hi.sign,
536            hi_scaled - lo_scaled - 1,
537            target_exp,
538            true,
539            f,
540            mode,
541        );
542    }
543    if hi_scaled == lo_scaled {
544        // Exact cancellation. IEEE 754 §6.3: the sum is +0 in every mode
545        // except toward −∞.
546        return zero(matches!(mode, Rounding::TowardMinusInf), f);
547    }
548    let (sign, mag) = if hi_scaled > lo_scaled {
549        (hi.sign, hi_scaled - lo_scaled)
550    } else {
551        (lo.sign, lo_scaled - hi_scaled)
552    };
553    round_pack(sign, mag, target_exp, false, f, mode)
554}
555
556/// `a × b`.
557///
558/// The product of two significands is **exact** in `u128`: at most 53 × 53 =
559/// 106 bits. So there is no sticky bit to carry here, and every flag comes from
560/// the single rounding.
561#[must_use]
562pub fn mul(a_bits: u64, b_bits: u64, f: Format, mode: Rounding) -> Rounded {
563    let a = unpack(a_bits, f);
564    let b = unpack(b_bits, f);
565    if a.class == Class::Nan || b.class == Class::Nan {
566        return nan_result(a, b, f);
567    }
568    let sign = a.sign ^ b.sign;
569    if a.class == Class::Inf || b.class == Class::Inf {
570        // 0 × ∞ is the undefined form.
571        if a.class == Class::Zero || b.class == Class::Zero {
572            return invalid(f);
573        }
574        return inf(sign, f);
575    }
576    if a.class == Class::Zero || b.class == Class::Zero {
577        return zero(sign, f);
578    }
579    round_pack(sign, a.sig * b.sig, a.exp + b.exp, false, f, mode)
580}
581
582/// `a ÷ b`.
583///
584/// A quotient is generally non-terminating in binary, so unlike the other three
585/// operations this one *must* carry a sticky bit: the division's remainder is
586/// exactly the information the native operator throws away.
587#[must_use]
588pub fn div(a_bits: u64, b_bits: u64, f: Format, mode: Rounding) -> Rounded {
589    let a = unpack(a_bits, f);
590    let b = unpack(b_bits, f);
591    if a.class == Class::Nan || b.class == Class::Nan {
592        return nan_result(a, b, f);
593    }
594    let sign = a.sign ^ b.sign;
595    match (a.class, b.class) {
596        // ∞/∞ and 0/0 are undefined forms; they are NOT division by zero.
597        (Class::Inf, Class::Inf) | (Class::Zero, Class::Zero) => return invalid(f),
598        (Class::Inf, _) => return inf(sign, f),
599        (_, Class::Inf) | (Class::Zero, _) => return zero(sign, f),
600        (_, Class::Zero) => {
601            // A finite non-zero numerator over zero. This is the ONLY case that
602            // raises DivideByZero: the flag marks an infinity created out of
603            // finite operands, which is why `∞/0` above does not qualify.
604            let mut flags = Flags::NONE;
605            flags.div_by_zero = true;
606            return Rounded {
607                bits: inf(sign, f).bits,
608                flags,
609            };
610        }
611        _ => {}
612    }
613
614    // Normalize both significands to bit 63 so the quotient always carries at
615    // least 64 significant bits — enough for `p + 2` in either format. Without
616    // this a subnormal numerator over a large divisor yields a quotient of only
617    // a dozen bits and the rounding below has nothing to round.
618    let (na, nae) = norm_to_63(a.sig, a.exp);
619    let (nb, nbe) = norm_to_63(b.sig, b.exp);
620
621    let num = na << 64;
622    let q = num / nb;
623    let r = num % nb;
624    round_pack(sign, q, nae - nbe - 64, r != 0, f, mode)
625}
626
627/// `SQRT.fmt` — correctly rounded, with exact flags.
628///
629/// # How
630///
631/// `value = m x 2^e`. Force `e` even (shifting `m` left compensates), then
632/// scale `m` up by `2^62` so the integer square root has ~64 significant bits
633/// — comfortably more than the `p + 2` correct rounding needs:
634///
635/// ```text
636/// sqrt(m x 2^e) = sqrt(m x 2^62) x 2^(e/2 - 31)
637/// ```
638///
639/// `u128::isqrt` gives the floor of that root, and the root is exact precisely
640/// when `q * q == n` — so that comparison **is** the sticky bit, with no
641/// tolerance and no second rounding. `q < 2^64`, so the square cannot overflow
642/// `u128`.
643///
644/// # Signs
645///
646/// `sqrt(-0)` is `-0`, not a NaN: the sign is preserved and nothing is raised.
647/// Any *other* negative operand is Invalid. Collapsing the two is a common
648/// error and IEEE is explicit about the exception.
649#[must_use]
650pub fn sqrt(bits: u64, f: Format, mode: Rounding) -> Rounded {
651    let a = unpack(bits, f);
652    match a.class {
653        Class::Nan => {
654            let mut flags = Flags::NONE;
655            flags.invalid = a.snan;
656            Rounded {
657                bits: f.default_nan(),
658                flags,
659            }
660        }
661        // Both zeros come back unchanged, sign included.
662        Class::Zero => zero(a.sign, f),
663        // Every negative operand except `-0` is Invalid, infinity included;
664        // `-0` was already returned above with its sign intact.
665        Class::Inf | Class::Finite if a.sign => invalid(f),
666        Class::Inf => inf(false, f),
667        Class::Finite => {
668            let (mut m, mut e) = norm_to_63(a.sig, a.exp);
669            if e & 1 != 0 {
670                // `e` must be even to halve it exactly. Shifting `m` left one
671                // and decrementing `e` is the same value.
672                m <<= 1;
673                e -= 1;
674            }
675            // `m < 2^65`, so `m << 62 < 2^127` and the shift cannot overflow.
676            let n = m << 62;
677            let q = n.isqrt();
678            round_pack(false, q, e / 2 - 31, q * q != n, f, mode)
679        }
680    }
681}
682
683/// Convert between formats — `CVT.S.D` narrowing, `CVT.D.S` widening.
684///
685/// # Why a conversion belongs here and not in `fpu`
686///
687/// **Narrowing is an arithmetic operation.** `CVT.S.D` has to round a 53-bit
688/// significand into 24, so it can be inexact, can overflow to infinity, and can
689/// underflow into the subnormal range — and each of those depends on
690/// `FCSR.RM`. A `v as f32` cast reports none of it and rounds to nearest only,
691/// which is where accuracy ledger C-11 found this operation still sitting after
692/// the arithmetic had been fixed.
693///
694/// Widening cannot lose anything, but goes through the same path so there is
695/// one conversion rather than two that can disagree.
696///
697/// # NaN handling
698///
699/// A NaN operand yields the **target format's** default NaN, flagged Invalid
700/// when the operand signals. The VR4300's *other* NaN class — significand MSB
701/// clear, quiet by its convention (ledger C-12) — raises unimplemented
702/// operation instead and is rejected by the caller before reaching here, so it
703/// is deliberately not a case below.
704#[must_use]
705pub fn convert(bits: u64, from: Format, to: Format, mode: Rounding) -> Rounded {
706    let a = unpack(bits, from);
707    match a.class {
708        Class::Nan => {
709            let mut flags = Flags::NONE;
710            flags.invalid = a.snan;
711            Rounded {
712                bits: to.default_nan(),
713                flags,
714            }
715        }
716        Class::Inf => inf(a.sign, to),
717        Class::Zero => zero(a.sign, to),
718        // The significand and exponent are format-independent here: `unpack`
719        // has already turned them into a plain `sig * 2^exp`, so the only work
720        // is rounding that into the target's precision and range.
721        Class::Finite => round_pack(a.sign, a.sig, a.exp, false, to, mode),
722    }
723}
724
725/// Shift `sig` left until its most significant bit sits at bit 63, adjusting
726/// the exponent to match. Exact — it is a change of scale, not of value.
727fn norm_to_63(sig: u128, exp: i32) -> (u128, i32) {
728    debug_assert!(sig != 0);
729    // Significands are at most 53 bits, so this shift is always to the left.
730    let sh = sig.leading_zeros() as i32 - 64;
731    debug_assert!(sh > 0);
732    (sig << sh, exp - sh)
733}
734
735#[cfg(test)]
736mod tests {
737    use super::*;
738
739    fn as32(r: Rounded) -> f32 {
740        f32::from_bits(r.bits as u32)
741    }
742    fn as64(r: Rounded) -> f64 {
743        f64::from_bits(r.bits)
744    }
745    fn b32(v: f32) -> u64 {
746        u64::from(v.to_bits())
747    }
748
749    /// A deterministic PRNG — ADR 0004 forbids entropy, and a differential test
750    /// that cannot be replayed is not evidence.
751    struct SplitMix(u64);
752    impl SplitMix {
753        fn next(&mut self) -> u64 {
754            self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
755            let mut z = self.0;
756            z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
757            z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
758            z ^ (z >> 31)
759        }
760    }
761
762    // --- Differential against the native operators ---------------------------
763
764    /// **The load-bearing test.** In round-to-nearest-even the soft-float
765    /// result must be *bit-identical* to the hardware operator for every one of
766    /// a large pseudo-random corpus, in both formats and all four operations.
767    ///
768    /// This is what makes the flags trustworthy: the flags come from the same
769    /// rounding step as the value, so a value that matches an independent
770    /// oracle bit-for-bit is strong evidence that the guard/sticky bookkeeping
771    /// the flags are read from is right. Testing the flags alone would be
772    /// self-referential — there is no other implementation here to disagree
773    /// with.
774    ///
775    /// NaN results are compared as "both are NaN": the VR4300's default NaN is
776    /// deliberately not the one Rust produces, and that difference is the
777    /// subject of its own test below.
778    #[test]
779    fn every_operation_matches_the_native_operator_bit_for_bit_in_round_to_nearest() {
780        let mut rng = SplitMix(0x5EED);
781        for _ in 0..40_000 {
782            let ab = rng.next() as u32;
783            let bb = rng.next() as u32;
784            let (x, y) = (f32::from_bits(ab), f32::from_bits(bb));
785            for (op, want, got) in [
786                (
787                    "add",
788                    x + y,
789                    as32(add(u64::from(ab), u64::from(bb), F32, Rounding::Nearest)),
790                ),
791                (
792                    "sub",
793                    x - y,
794                    as32(sub(u64::from(ab), u64::from(bb), F32, Rounding::Nearest)),
795                ),
796                (
797                    "mul",
798                    x * y,
799                    as32(mul(u64::from(ab), u64::from(bb), F32, Rounding::Nearest)),
800                ),
801                (
802                    "div",
803                    x / y,
804                    as32(div(u64::from(ab), u64::from(bb), F32, Rounding::Nearest)),
805                ),
806            ] {
807                if want.is_nan() {
808                    assert!(got.is_nan(), "{op}: {x:e} {y:e} -> want NaN, got {got:e}");
809                } else {
810                    assert_eq!(
811                        got.to_bits(),
812                        want.to_bits(),
813                        "f32 {op}: {x:e} ({ab:#010X}) op {y:e} ({bb:#010X})"
814                    );
815                }
816            }
817
818            let (ab, bb) = (rng.next(), rng.next());
819            let (x, y) = (f64::from_bits(ab), f64::from_bits(bb));
820            for (op, want, got) in [
821                ("add", x + y, as64(add(ab, bb, F64, Rounding::Nearest))),
822                ("sub", x - y, as64(sub(ab, bb, F64, Rounding::Nearest))),
823                ("mul", x * y, as64(mul(ab, bb, F64, Rounding::Nearest))),
824                ("div", x / y, as64(div(ab, bb, F64, Rounding::Nearest))),
825            ] {
826                if want.is_nan() {
827                    assert!(got.is_nan(), "{op}: {x:e} {y:e}");
828                } else {
829                    assert_eq!(
830                        got.to_bits(),
831                        want.to_bits(),
832                        "f64 {op}: {x:e} ({ab:#018X}) op {y:e} ({bb:#018X})"
833                    );
834                }
835            }
836        }
837    }
838
839    /// The random corpus above is dominated by huge and tiny magnitudes, which
840    /// is good for exponent handling and bad for coverage of the ordinary
841    /// range. This one draws small integers and simple fractions, where
842    /// cancellation and exactness actually occur.
843    #[test]
844    fn the_ordinary_numeric_range_matches_the_native_operator_too() {
845        let mut rng = SplitMix(0xC0FFEE);
846        for _ in 0..40_000 {
847            let x = (rng.next() % 2001) as f32 / 8.0 - 125.0;
848            let y = (rng.next() % 2001) as f32 / 8.0 - 125.0;
849            let (xb, yb) = (b32(x), b32(y));
850            assert_eq!(
851                as32(add(xb, yb, F32, Rounding::Nearest)).to_bits(),
852                (x + y).to_bits(),
853                "{x} + {y}"
854            );
855            assert_eq!(
856                as32(sub(xb, yb, F32, Rounding::Nearest)).to_bits(),
857                (x - y).to_bits(),
858                "{x} - {y}"
859            );
860            assert_eq!(
861                as32(mul(xb, yb, F32, Rounding::Nearest)).to_bits(),
862                (x * y).to_bits(),
863                "{x} * {y}"
864            );
865            let q = div(xb, yb, F32, Rounding::Nearest);
866            if !(x / y).is_nan() {
867                assert_eq!(as32(q).to_bits(), (x / y).to_bits(), "{x} / {y}");
868            }
869        }
870    }
871
872    /// Subnormal results are where the `f64`-comparison shortcut breaks, so
873    /// they get their own sweep rather than being left to chance.
874    #[test]
875    fn the_subnormal_range_matches_the_native_operator() {
876        let mut rng = SplitMix(0xD3ADB33F);
877        for _ in 0..20_000 {
878            // Magnitudes just above and below f32's smallest normal.
879            let a = f32::from_bits((rng.next() as u32 & 0x007F_FFFF) | 0x0080_0000);
880            let b = f32::from_bits(rng.next() as u32 & 0x00FF_FFFF);
881            let (ab, bb) = (b32(a), b32(b));
882            assert_eq!(
883                as32(add(ab, bb, F32, Rounding::Nearest)).to_bits(),
884                (a + b).to_bits()
885            );
886            assert_eq!(
887                as32(sub(ab, bb, F32, Rounding::Nearest)).to_bits(),
888                (a - b).to_bits()
889            );
890            assert_eq!(
891                as32(mul(ab, bb, F32, Rounding::Nearest)).to_bits(),
892                (a * b).to_bits()
893            );
894        }
895    }
896
897    // --- Flags ---------------------------------------------------------------
898
899    /// An exactly representable result raises **nothing**. This is the control
900    /// for every inexact assertion below: an implementation that flagged
901    /// everything inexact would satisfy them all.
902    #[test]
903    fn an_exact_operation_raises_no_flags() {
904        let r = add(b32(1.0), b32(2.0), F32, Rounding::Nearest);
905        assert_eq!(as32(r), 3.0);
906        assert_eq!(r.flags, Flags::NONE, "1 + 2 is exact");
907        let r = mul(b32(3.0), b32(0.5), F32, Rounding::Nearest);
908        assert_eq!(r.flags, Flags::NONE, "3 * 0.5 is exact");
909        let r = div(b32(1.0), b32(4.0), F32, Rounding::Nearest);
910        assert_eq!(r.flags, Flags::NONE, "1 / 4 terminates");
911    }
912
913    /// **The case ledger C-11 is about**, verbatim from n64-systemtest:
914    /// `f32::MIN + (-1.0)` returns `f32::MIN` and must raise Inexact.
915    ///
916    /// The result is *unchanged*, which is exactly why the flag is the only
917    /// observable: an implementation that returns the right value and no flag
918    /// looks correct until `FCSR` is read back.
919    #[test]
920    fn the_c11_case_raises_inexact_though_the_value_is_unchanged() {
921        for (a, b) in [
922            (f32::MIN, -1.0f32),
923            (f32::MAX, -1.0f32),
924            (f32::MAX, 1.0f32),
925            (f32::MAX, f32::MIN_POSITIVE),
926        ] {
927            let r = add(b32(a), b32(b), F32, Rounding::Nearest);
928            assert_eq!(as32(r), a + b, "value");
929            assert!(r.flags.inexact, "{a:e} + {b:e} must raise Inexact");
930            assert!(!r.flags.overflow, "and must not overflow");
931            assert!(!r.flags.underflow, "nor underflow");
932        }
933    }
934
935    /// `1/3` does not terminate in binary, so the sticky bit must survive the
936    /// division. This is the flag the native operator cannot report at all.
937    #[test]
938    fn a_non_terminating_quotient_is_inexact() {
939        let r = div(b32(1.0), b32(3.0), F32, Rounding::Nearest);
940        assert!(r.flags.inexact, "1/3 is inexact");
941        assert_eq!(as32(r), 1.0f32 / 3.0);
942        // ...and one that does terminate is not, so the flag is not simply
943        // always set on division.
944        assert!(
945            !div(b32(1.0), b32(2.0), F32, Rounding::Nearest)
946                .flags
947                .inexact
948        );
949    }
950
951    /// Overflow implies Inexact — they are not independent, and reporting
952    /// overflow alone leaves `FCSR` in a state hardware never produces.
953    #[test]
954    fn overflow_is_also_inexact_and_saturates_per_rounding_mode() {
955        let big = b32(3e38);
956        let r = add(big, b32(8e37), F32, Rounding::Nearest);
957        assert!(r.flags.overflow && r.flags.inexact);
958        assert_eq!(as32(r), f32::INFINITY);
959
960        // Toward zero cannot reach infinity: it saturates at MAX.
961        let r = add(big, b32(8e37), F32, Rounding::TowardZero);
962        assert!(r.flags.overflow && r.flags.inexact);
963        assert_eq!(as32(r), f32::MAX, "toward zero saturates, it does not inf");
964
965        // Toward −∞ on a positive overflow likewise stops at MAX.
966        let r = add(big, b32(8e37), F32, Rounding::TowardMinusInf);
967        assert_eq!(as32(r), f32::MAX);
968        // ...but a negative overflow in the same mode does reach −∞.
969        let r = add(b32(-3e38), b32(-8e37), F32, Rounding::TowardMinusInf);
970        assert_eq!(as32(r), f32::NEG_INFINITY);
971    }
972
973    /// Underflow is signaled when the result is tiny **and** inexact — an
974    /// exact subnormal result raises neither.
975    #[test]
976    fn a_directed_rounding_out_of_the_subnormal_range_still_underflows() {
977        // FLT_MIN / (1 + 1ulp) is tiny and inexact; rounding toward +inf pushes
978        // it back up to FLT_MIN, a perfectly NORMAL number.
979        //
980        // Deciding underflow from the packed result therefore misses it — the
981        // result is normal — while the hardware raises it, because the value it
982        // rounded *from* was tiny. IEEE 754 permits either convention and the
983        // VR4300 detects tininess BEFORE rounding.
984        let r = div(
985            f32::MIN_POSITIVE.to_bits().into(),
986            1.000_000_1f32.to_bits().into(),
987            F32,
988            Rounding::TowardPlusInf,
989        );
990        assert_eq!(
991            r.bits as u32,
992            f32::MIN_POSITIVE.to_bits(),
993            "rounds back up to the smallest normal"
994        );
995        assert!(r.flags.inexact);
996        assert!(
997            r.flags.underflow,
998            "tiny before rounding, so underflow stands even though the result is normal"
999        );
1000    }
1001
1002    #[test]
1003    fn underflow_needs_both_tininess_and_inexactness() {
1004        // Two subnormals whose sum is exactly representable: tiny, exact.
1005        let a = f32::from_bits(3);
1006        let b = f32::from_bits(4);
1007        let r = add(b32(a), b32(b), F32, Rounding::Nearest);
1008        assert_eq!(as32(r), f32::from_bits(7));
1009        assert!(!r.flags.underflow, "an exact subnormal does not underflow");
1010        assert!(!r.flags.inexact);
1011
1012        // A product that lands below the subnormal grid: tiny and inexact.
1013        let r = mul(b32(f32::from_bits(3)), b32(0.5), F32, Rounding::Nearest);
1014        assert!(
1015            r.flags.underflow && r.flags.inexact,
1016            "flags = {:?}",
1017            r.flags
1018        );
1019    }
1020
1021    /// The four IEEE special forms, kept apart. `∞/0` in particular is **not**
1022    /// `DivideByZero`: that flag marks an infinity conjured from finite operands.
1023    #[test]
1024    fn the_invalid_and_divide_by_zero_forms_are_distinguished() {
1025        let nan = |r: Rounded| as32(r).is_nan();
1026
1027        assert!(nan(add(
1028            b32(f32::INFINITY),
1029            b32(f32::NEG_INFINITY),
1030            F32,
1031            Rounding::Nearest
1032        )));
1033        assert!(
1034            add(
1035                b32(f32::INFINITY),
1036                b32(f32::NEG_INFINITY),
1037                F32,
1038                Rounding::Nearest
1039            )
1040            .flags
1041            .invalid
1042        );
1043        assert!(
1044            mul(b32(0.0), b32(f32::INFINITY), F32, Rounding::Nearest)
1045                .flags
1046                .invalid
1047        );
1048        assert!(
1049            div(b32(0.0), b32(0.0), F32, Rounding::Nearest)
1050                .flags
1051                .invalid
1052        );
1053        assert!(
1054            div(
1055                b32(f32::INFINITY),
1056                b32(f32::INFINITY),
1057                F32,
1058                Rounding::Nearest
1059            )
1060            .flags
1061            .invalid
1062        );
1063
1064        let dz = div(b32(1.0), b32(0.0), F32, Rounding::Nearest);
1065        assert!(dz.flags.div_by_zero, "finite / 0 is DivideByZero");
1066        assert!(!dz.flags.invalid, "and not Invalid");
1067        assert_eq!(as32(dz), f32::INFINITY);
1068
1069        let iz = div(b32(f32::INFINITY), b32(0.0), F32, Rounding::Nearest);
1070        assert!(!iz.flags.div_by_zero, "inf / 0 was already infinite");
1071        assert_eq!(as32(iz), f32::INFINITY);
1072    }
1073
1074    /// An invalid operation delivers the **VR4300's** default NaN, which has the
1075    /// quiet bit clear and is therefore not the NaN Rust would produce.
1076    #[test]
1077    fn an_invalid_operation_delivers_the_vr4300_default_nan() {
1078        let r = add(
1079            b32(f32::INFINITY),
1080            b32(f32::NEG_INFINITY),
1081            F32,
1082            Rounding::Nearest,
1083        );
1084        assert_eq!(r.bits, 0x7FBF_FFFF, "the value n64-systemtest expects");
1085        let r = add(
1086            f64::INFINITY.to_bits(),
1087            f64::NEG_INFINITY.to_bits(),
1088            F64,
1089            Rounding::Nearest,
1090        );
1091        assert_eq!(r.bits, 0x7FF7_FFFF_FFFF_FFFF);
1092    }
1093
1094    /// A **signaling** NaN operand raises Invalid; a quiet one propagates
1095    /// silently. Treating every NaN as signaling raises Invalid on ordinary
1096    /// NaN propagation, which is a common and invisible error.
1097    #[test]
1098    fn only_a_signaling_nan_operand_raises_invalid() {
1099        // **Inverted from IEEE**: on the VR4300 the significand MSB set means
1100        // *signaling*. See `fpu::is_snan_f32` and ledger C-12.
1101        let snan = 0x7FC0_0000u64; // MSB set   -> signaling here
1102        let qnan = 0x7FA0_0000u64; // MSB clear -> quiet here
1103        assert!(add(snan, b32(1.0), F32, Rounding::Nearest).flags.invalid);
1104        assert!(!add(qnan, b32(1.0), F32, Rounding::Nearest).flags.invalid);
1105        assert!(mul(b32(1.0), snan, F32, Rounding::Nearest).flags.invalid);
1106    }
1107
1108    // --- Rounding modes ------------------------------------------------------
1109
1110    /// The four modes bracket an inexact result: toward −∞ ≤ nearest ≤
1111    /// toward +∞, and toward zero equals one of the outer two by sign.
1112    #[test]
1113    fn the_directed_modes_bracket_the_nearest_result() {
1114        // 1e15 + 5e-20 is inexact in f32 and n64-systemtest tests it directly.
1115        let (a, b) = (b32(1e15), b32(5e-20));
1116        let down = as32(add(a, b, F32, Rounding::TowardMinusInf));
1117        let near = as32(add(a, b, F32, Rounding::Nearest));
1118        let up = as32(add(a, b, F32, Rounding::TowardPlusInf));
1119        let zero = as32(add(a, b, F32, Rounding::TowardZero));
1120        assert!(down <= near && near <= up, "{down:e} {near:e} {up:e}");
1121        assert!(down < up, "an inexact result must differ between the modes");
1122        assert_eq!(zero, down, "toward zero == toward −∞ for a positive value");
1123    }
1124
1125    /// The exact n64-systemtest expectations for the `ADD.S` rounding-mode
1126    /// cases. These are golden vectors from an independent oracle, not values
1127    /// this implementation produced — which is the only kind that can falsify
1128    /// it (module 20, *Golden vectors*).
1129    #[test]
1130    fn the_n64_systemtest_rounding_vectors_hold() {
1131        let cases: &[(f32, f32, Rounding, f32)] = &[
1132            (1e15, 5e-20, Rounding::Nearest, 1e15),
1133            (1e15, 5e-20, Rounding::TowardZero, 1e15),
1134            (1e15, 5e-20, Rounding::TowardPlusInf, 1000000050000000f32),
1135            (1e15, 5e-20, Rounding::TowardMinusInf, 1e15),
1136            (-1e15, -5e-20, Rounding::Nearest, -1e15),
1137            (-1e15, -5e-20, Rounding::TowardZero, -1e15),
1138            (-1e15, -5e-20, Rounding::TowardPlusInf, -1e15),
1139            (
1140                -1e15,
1141                -5e-20,
1142                Rounding::TowardMinusInf,
1143                -1000000050000000f32,
1144            ),
1145            (1e15, 33500000f32, Rounding::Nearest, 1e15),
1146            (1e15, 33600000f32, Rounding::Nearest, 1000000050000000f32),
1147            (1e15, 33500000f32, Rounding::TowardZero, 1e15),
1148            (1e15, 33600000f32, Rounding::TowardZero, 1e15),
1149        ];
1150        for &(a, b, mode, want) in cases {
1151            let r = add(b32(a), b32(b), F32, mode);
1152            assert_eq!(
1153                as32(r).to_bits(),
1154                want.to_bits(),
1155                "{a:e} + {b:e} under {mode:?}"
1156            );
1157            assert!(r.flags.inexact, "{a:e} + {b:e} under {mode:?} is inexact");
1158        }
1159    }
1160
1161    /// Ties-to-even resolves *to even*, not away from zero. `f32` has 24
1162    /// significand bits, so `2^24 + 1` is exactly a tie.
1163    #[test]
1164    fn a_tie_rounds_to_even_not_away_from_zero() {
1165        let two24 = b32(16_777_216.0); // 2^24
1166        // 2^24 + 1 is a tie between 2^24 and 2^24+2; even wins.
1167        let r = add(two24, b32(1.0), F32, Rounding::Nearest);
1168        assert_eq!(as32(r), 16_777_216.0, "ties down to the even value");
1169        assert!(r.flags.inexact);
1170        // 2^24 + 3 ties between 2^24+2 and 2^24+4; even is +4.
1171        let r = add(two24, b32(3.0), F32, Rounding::Nearest);
1172        assert_eq!(as32(r), 16_777_220.0, "ties up to the even value");
1173    }
1174
1175    // --- Signed zero ---------------------------------------------------------
1176
1177    /// The sign of a zero sum is mode-dependent, and it is the one place a
1178    /// rounding mode changes a result that is otherwise exact.
1179    #[test]
1180    fn the_sign_of_a_canceled_zero_follows_the_rounding_mode() {
1181        let r = add(b32(1.0), b32(-1.0), F32, Rounding::Nearest);
1182        assert_eq!(r.bits, 0, "+0 in round-to-nearest");
1183        let r = add(b32(1.0), b32(-1.0), F32, Rounding::TowardMinusInf);
1184        assert_eq!(r.bits, 0x8000_0000, "−0 toward −∞");
1185        let r = add(b32(-0.0), b32(-0.0), F32, Rounding::Nearest);
1186        assert_eq!(r.bits, 0x8000_0000, "(−0) + (−0) is −0 in every mode");
1187    }
1188
1189    /// Narrowing must match the native `as f32` in round-to-nearest, across the
1190    /// same three corpora the arithmetic uses — including the subnormal
1191    /// boundary, where a naive implementation double-rounds.
1192    #[test]
1193    fn narrowing_matches_the_native_cast_in_round_to_nearest() {
1194        let mut rng = SplitMix(0xC0DE_1234);
1195        for _ in 0..40_000 {
1196            let bits = rng.next();
1197            let v = f64::from_bits(bits);
1198            let got = convert(bits, F64, F32, Rounding::Nearest);
1199            let want = v as f32;
1200            if want.is_nan() {
1201                assert!(f32::from_bits(got.bits as u32).is_nan(), "{v:e}");
1202            } else {
1203                assert_eq!(got.bits as u32, want.to_bits(), "narrowing {v:e}");
1204            }
1205        }
1206        // Widening is exact for every f32, so it must round-trip.
1207        for _ in 0..20_000 {
1208            let b = rng.next() as u32;
1209            let v = f32::from_bits(b);
1210            let got = convert(u64::from(b), F32, F64, Rounding::Nearest);
1211            if v.is_nan() {
1212                assert!(f64::from_bits(got.bits).is_nan());
1213            } else {
1214                assert_eq!(got.bits, f64::from(v).to_bits(), "widening {v:e}");
1215                assert_eq!(got.flags, Flags::NONE, "widening is always exact");
1216            }
1217        }
1218    }
1219
1220    /// The n64-systemtest `CVT.S.D` vectors: a value that needs rounding must
1221    /// differ between the modes, and one past `f32::MAX` must overflow the way
1222    /// the mode directs.
1223    #[test]
1224    fn narrowing_honors_the_rounding_mode_and_overflows_per_mode() {
1225        let v = 4.123_456_789_123_456_f64.to_bits();
1226        let down = convert(v, F64, F32, Rounding::TowardMinusInf);
1227        let near = convert(v, F64, F32, Rounding::Nearest);
1228        let up = convert(v, F64, F32, Rounding::TowardPlusInf);
1229        assert!(down.flags.inexact && near.flags.inexact && up.flags.inexact);
1230        assert_ne!(
1231            down.bits, up.bits,
1232            "the modes must disagree on an inexact value"
1233        );
1234        assert_eq!(
1235            convert(v, F64, F32, Rounding::TowardZero).bits,
1236            down.bits,
1237            "toward zero == toward -inf for a positive value"
1238        );
1239
1240        // Just past f32::MAX.
1241        let big = 3.402_823_48e38_f64.to_bits();
1242        let r = convert(big, F64, F32, Rounding::TowardPlusInf);
1243        assert!(r.flags.overflow && r.flags.inexact);
1244        assert_eq!(f32::from_bits(r.bits as u32), f32::INFINITY);
1245        let r = convert(big, F64, F32, Rounding::TowardZero);
1246        assert_eq!(
1247            f32::from_bits(r.bits as u32),
1248            f32::MAX,
1249            "toward zero saturates"
1250        );
1251    }
1252
1253    /// A double inside `f32`'s **subnormal** range must narrow to an actual
1254    /// subnormal and report underflow — the case the VR4300 then refuses
1255    /// outright (ledger C-13), which it can only do if this reports it.
1256    ///
1257    /// The first draft used `f64::MIN_POSITIVE`, which is ~2.2e-308 and narrows
1258    /// to plain **zero** — far below `f32`'s entire range, so it never produced
1259    /// a subnormal and did not test what its name claimed. `1e-40` sits between
1260    /// `f32`'s smallest subnormal (~1.4e-45) and its smallest normal
1261    /// (~1.18e-38), which is the band that matters.
1262    #[test]
1263    fn narrowing_into_the_subnormal_range_reports_underflow() {
1264        let r = convert(1e-40_f64.to_bits(), F64, F32, Rounding::Nearest);
1265        let got = f32::from_bits(r.bits as u32);
1266        assert!(got != 0.0, "must not flush to zero: {got:e}");
1267        assert!(
1268            got.to_bits() & 0x7F80_0000 == 0,
1269            "and must be an actual subnormal: {got:e}"
1270        );
1271        assert!(
1272            r.flags.underflow && r.flags.inexact,
1273            "flags = {:?}",
1274            r.flags
1275        );
1276
1277        // Below the whole range it does reach zero, still underflowing.
1278        let r = convert(f64::MIN_POSITIVE.to_bits(), F64, F32, Rounding::Nearest);
1279        assert_eq!(f32::from_bits(r.bits as u32), 0.0, "far below f32's range");
1280        assert!(r.flags.underflow && r.flags.inexact);
1281    }
1282
1283    /// `SQRT` must be bit-identical to the native square root across the same
1284    /// corpora as the arithmetic, in both formats.
1285    ///
1286    /// `f32::sqrt` lives in `std` and this crate is `#![no_std]`, so the
1287    /// reference is pulled in for the test only.
1288    #[test]
1289    fn sqrt_matches_the_native_square_root_in_round_to_nearest() {
1290        extern crate std;
1291        let mut rng = SplitMix(0x5017_5017);
1292        for _ in 0..40_000 {
1293            let ab = rng.next() as u32;
1294            let x = f32::from_bits(ab);
1295            let got = as32(sqrt(u64::from(ab), F32, Rounding::Nearest));
1296            let want = std::primitive::f32::sqrt(x);
1297            if want.is_nan() {
1298                assert!(got.is_nan(), "sqrt({x:e}) want NaN, got {got:e}");
1299            } else {
1300                assert_eq!(got.to_bits(), want.to_bits(), "sqrt({x:e})");
1301            }
1302
1303            let bb = rng.next();
1304            let y = f64::from_bits(bb);
1305            let got = as64(sqrt(bb, F64, Rounding::Nearest));
1306            let want = std::primitive::f64::sqrt(y);
1307            if want.is_nan() {
1308                assert!(got.is_nan(), "sqrt({y:e})");
1309            } else {
1310                assert_eq!(got.to_bits(), want.to_bits(), "sqrt({y:e})");
1311            }
1312        }
1313        // Small positive integers, where exact results and ties actually occur.
1314        for i in 0..20_000u32 {
1315            let x = f32::from(i as u16) + 0.25;
1316            let got = as32(sqrt(b32(x), F32, Rounding::Nearest));
1317            assert_eq!(
1318                got.to_bits(),
1319                std::primitive::f32::sqrt(x).to_bits(),
1320                "sqrt({x})"
1321            );
1322        }
1323    }
1324
1325    /// The n64-systemtest `SQRT.S` vectors, including the signs IEEE is
1326    /// explicit about and the directed-rounding split on `f32::MAX`.
1327    #[test]
1328    fn the_n64_systemtest_sqrt_vectors_hold() {
1329        // Exact results raise nothing.
1330        let r = sqrt(b32(4.0), F32, Rounding::Nearest);
1331        assert_eq!(as32(r), 2.0);
1332        assert_eq!(r.flags, Flags::NONE, "sqrt(4) is exact");
1333
1334        // sqrt(2) is inexact.
1335        assert!(sqrt(b32(2.0), F32, Rounding::Nearest).flags.inexact);
1336
1337        // **sqrt(-0) is -0**, not a NaN, and raises nothing.
1338        let r = sqrt(b32(-0.0), F32, Rounding::Nearest);
1339        assert_eq!(r.bits, 0x8000_0000, "sqrt(-0) = -0");
1340        assert_eq!(r.flags, Flags::NONE);
1341        // Every other negative is Invalid.
1342        for v in [-4.0f32, f32::MIN, -f32::MIN_POSITIVE, f32::NEG_INFINITY] {
1343            let r = sqrt(b32(v), F32, Rounding::Nearest);
1344            assert!(r.flags.invalid, "sqrt({v:e}) must be Invalid");
1345            assert_eq!(r.bits, 0x7FBF_FFFF, "and give the VR4300 default NaN");
1346        }
1347
1348        assert_eq!(
1349            as32(sqrt(b32(f32::INFINITY), F32, Rounding::Nearest)),
1350            f32::INFINITY
1351        );
1352
1353        // The rounding mode splits sqrt(f32::MAX) between two neighbors.
1354        let near = as32(sqrt(b32(f32::MAX), F32, Rounding::Nearest));
1355        let up = as32(sqrt(b32(f32::MAX), F32, Rounding::TowardPlusInf));
1356        let down = as32(sqrt(b32(f32::MAX), F32, Rounding::TowardMinusInf));
1357        assert_eq!(near.to_bits(), 1.8446743e19f32.to_bits());
1358        assert_eq!(up.to_bits(), 1.8446744e19f32.to_bits());
1359        assert_eq!(down.to_bits(), 1.8446743e19f32.to_bits());
1360
1361        // A normal operand whose root is exact raises nothing.
1362        let r = sqrt(b32(f32::MIN_POSITIVE), F32, Rounding::Nearest);
1363        assert_eq!(as32(r).to_bits(), 1.0842022e-19f32.to_bits());
1364        assert_eq!(r.flags, Flags::NONE);
1365    }
1366
1367    /// Format constants, asserted rather than assumed — every derived quantity
1368    /// in this module is computed from them.
1369    #[test]
1370    fn the_format_parameters_are_right() {
1371        assert_eq!(F32.max_biased(), 255);
1372        assert_eq!(F64.max_biased(), 2047);
1373        assert_eq!(
1374            F32.min_lsb_exp(),
1375            -149,
1376            "f32's smallest subnormal is 2^-149"
1377        );
1378        assert_eq!(F64.min_lsb_exp(), -1074);
1379        assert_eq!(F32.infinity(), 0x7F80_0000);
1380        assert_eq!(F32.max_finite(), 0x7F7F_FFFF);
1381        assert_eq!(F64.max_finite(), 0x7FEF_FFFF_FFFF_FFFF);
1382        assert_eq!(F32.default_nan(), 0x7FBF_FFFF);
1383        assert_eq!(F64.default_nan(), 0x7FF7_FFFF_FFFF_FFFF);
1384    }
1385}