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}