mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 16:24:45 +00:00
The previous portability fix bounded the powi test's drift from std::powi at an absolute ULP count, but Windows/MSVC drift grows with the exponent (3 ULP by exp=14), so no fixed ULP bound is portable. Replace the ULP oracle with a loose relative-tolerance sanity sweep (1e-11), which any sane libm clears by ~1000x regardless of exponent while still catching a grossly wrong impl. Add a golden-bit pin for the exp=14 case that exposed the growth. The powi function is unchanged; the golden-bit determinism pins already passed on Windows.
99 lines
4.0 KiB
Rust
99 lines
4.0 KiB
Rust
//! no_std-shaped numeric helpers for the protocol cores.
|
|
//!
|
|
//! `f64::powi` and the transcendental methods live in `std`, not `core`. The
|
|
//! helpers here keep the small amount of protocol math the codecs need in a
|
|
//! `core`-only shape so the cores stay portable, without changing any result.
|
|
|
|
/// Raise `base` to a non-negative integer power via square-and-multiply.
|
|
///
|
|
/// Built from `core`-only IEEE-754 `f64` multiplication, so the result is
|
|
/// **deterministic across every platform** — the property that matters here,
|
|
/// since the bloom false-positive-rate feeds a reject comparison and the FMP
|
|
/// backoff feeds a `u64` timer, and mesh nodes must agree regardless of OS.
|
|
/// This is a strict improvement over `f64::powi`, which is not portably
|
|
/// bit-stable: Linux and macOS lower it to compiler-rt's `__powidf2` (whose
|
|
/// multiply-then-square order this mirrors, so we match them exactly), but
|
|
/// Windows/MSVC rounds differently (and by more as the exponent grows). The
|
|
/// `powi_deterministic_and_close` test pins our output to golden bits and bounds
|
|
/// the relative drift from `std`.
|
|
pub(crate) fn powi(base: f64, exp: u32) -> f64 {
|
|
let mut result = 1.0_f64;
|
|
let mut b = base;
|
|
let mut e = exp;
|
|
loop {
|
|
if e & 1 == 1 {
|
|
result *= b;
|
|
}
|
|
e >>= 1;
|
|
if e == 0 {
|
|
break;
|
|
}
|
|
b *= b;
|
|
}
|
|
result
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::powi;
|
|
|
|
#[test]
|
|
fn powi_deterministic_and_close() {
|
|
// Determinism: our square-and-multiply is pure IEEE-754 f64 arithmetic,
|
|
// so these outputs are identical on every platform. Pinning the bits
|
|
// guards cross-node agreement and locks against future drift. (The bit
|
|
// patterns were captured from the impl itself; they also match Linux and
|
|
// macOS `f64::powi` — see the golden vs Windows note below.)
|
|
let golden = [
|
|
((0.5469, 4), 4591110734581567521u64),
|
|
((0.5, 2), 4598175219545276416),
|
|
((2.0, 10), 4652218415073722368),
|
|
((0.9999, 64), 4607124953935219716),
|
|
((1.5, 8), 4627907113471967232),
|
|
((3.7, 5), 4649310774781981293),
|
|
((0.1, 3), 4562254508917369341),
|
|
((0.5493, 4), 4591224636877479401),
|
|
((0.5508, 4), 4591296588123960286),
|
|
((0.5469, 14), 4552032634403898040),
|
|
];
|
|
for ((base, exp), bits) in golden {
|
|
assert_eq!(
|
|
powi(base, exp).to_bits(),
|
|
bits,
|
|
"powi determinism drift at base={base}, exp={exp}"
|
|
);
|
|
}
|
|
|
|
// Correctness: stay within a loose relative tolerance of `std::powi`
|
|
// across every base and exponent the protocol math reaches — a broad
|
|
// sanity sweep backing the exact golden pins above. `f64::powi` is not
|
|
// portably bit-stable: Windows/MSVC drifts from Linux/macOS, and that
|
|
// drift grows with the exponent (3 ULP already by exp=14), so an absolute
|
|
// ULP bound is not portable. Relative error of any sane libm stays around
|
|
// 1e-15 regardless of exponent, so 1e-11 clears the drift by ~1000x while
|
|
// still catching a grossly wrong impl (multiply-order subtleties are
|
|
// caught by the golden pins, not here).
|
|
let bases = [
|
|
0.0, 1.0, 0.5, 0.5469, 0.5493, 0.5508, 0.9999, 1.5, 2.0, 3.7, 0.1, 1e-3,
|
|
];
|
|
for &base in &bases {
|
|
for exp in 0u32..=64 {
|
|
let ours = powi(base, exp);
|
|
let std = base.powi(exp as i32);
|
|
if std == 0.0 {
|
|
assert_eq!(
|
|
ours, 0.0,
|
|
"powi zero-case mismatch at base={base}, exp={exp}"
|
|
);
|
|
} else {
|
|
let rel = ((ours - std) / std).abs();
|
|
assert!(
|
|
rel <= 1e-11,
|
|
"powi relative drift at base={base}, exp={exp}: ours={ours:e}, std={std:e}, rel={rel:e}"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|