also use nicer check_all_outcomes in float_nan

This commit is contained in:
Ralf Jung 2025-09-10 16:35:07 +02:00
parent 21e3111ef9
commit 655b847434

View file

@ -1,7 +1,8 @@
// This test's runtime explodes if the GC interval is set to 1 (which we do in CI), so we
// override it internally back to the default frequency.
//@compile-flags: -Zmiri-provenance-gc=10000
#![feature(float_gamma, portable_simd, core_intrinsics)]
use std::collections::HashSet;
use std::fmt;
use std::hash::Hash;
use std::hint::black_box;
fn ldexp(a: f64, b: i32) -> f64 {
@ -25,11 +26,18 @@ enum NaNKind {
}
use NaNKind::*;
/// Check that the function produces the intended set of outcomes.
#[track_caller]
fn check_all_outcomes<T: Eq + Hash + fmt::Display>(expected: HashSet<T>, generate: impl Fn() -> T) {
fn check_all_outcomes<T: Eq + std::hash::Hash + fmt::Display>(
expected: impl IntoIterator<Item = T>,
generate: impl Fn() -> T,
) {
use std::collections::HashSet;
let expected: HashSet<T> = HashSet::from_iter(expected);
let mut seen = HashSet::new();
// Let's give it sixteen times as many tries as we are expecting values.
let tries = expected.len() * 16;
// Let's give it N times as many tries as we are expecting values.
let tries = expected.len() * 12;
for _ in 0..tries {
let val = generate();
assert!(expected.contains(&val), "got an unexpected value: {val}");
@ -193,51 +201,50 @@ impl F64 {
fn test_f32() {
// Freshly generated NaNs can have either sign.
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(0.0 / black_box(0.0)),
);
check_all_outcomes([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)], || {
F32::from(0.0 / black_box(0.0))
});
// When there are NaN inputs, their payload can be propagated, with any sign.
let all1_payload = u32_ones(22);
let all1 = F32::nan(Pos, Quiet, all1_payload).as_f32();
check_all_outcomes(
HashSet::from_iter([
[
F32::nan(Pos, Quiet, 0),
F32::nan(Neg, Quiet, 0),
F32::nan(Pos, Quiet, all1_payload),
F32::nan(Neg, Quiet, all1_payload),
]),
],
|| F32::from(0.0 + all1),
);
// When there are two NaN inputs, the output can be either one, or the preferred NaN.
let just1 = F32::nan(Neg, Quiet, 1).as_f32();
check_all_outcomes(
HashSet::from_iter([
[
F32::nan(Pos, Quiet, 0),
F32::nan(Neg, Quiet, 0),
F32::nan(Pos, Quiet, 1),
F32::nan(Neg, Quiet, 1),
F32::nan(Pos, Quiet, all1_payload),
F32::nan(Neg, Quiet, all1_payload),
]),
],
|| F32::from(just1 - all1),
);
// When there are *signaling* NaN inputs, they might be quieted or not.
let all1_snan = F32::nan(Pos, Signaling, all1_payload).as_f32();
check_all_outcomes(
HashSet::from_iter([
[
F32::nan(Pos, Quiet, 0),
F32::nan(Neg, Quiet, 0),
F32::nan(Pos, Quiet, all1_payload),
F32::nan(Neg, Quiet, all1_payload),
F32::nan(Pos, Signaling, all1_payload),
F32::nan(Neg, Signaling, all1_payload),
]),
],
|| F32::from(0.0 * all1_snan),
);
// Mix signaling and non-signaling NaN.
check_all_outcomes(
HashSet::from_iter([
[
F32::nan(Pos, Quiet, 0),
F32::nan(Neg, Quiet, 0),
F32::nan(Pos, Quiet, 1),
@ -246,35 +253,26 @@ fn test_f32() {
F32::nan(Neg, Quiet, all1_payload),
F32::nan(Pos, Signaling, all1_payload),
F32::nan(Neg, Signaling, all1_payload),
]),
],
|| F32::from(just1 % all1_snan),
);
// Unary `-` must preserve payloads exactly.
check_all_outcomes(HashSet::from_iter([F32::nan(Neg, Quiet, all1_payload)]), || {
F32::from(-all1)
});
check_all_outcomes(HashSet::from_iter([F32::nan(Neg, Signaling, all1_payload)]), || {
F32::from(-all1_snan)
});
check_all_outcomes([F32::nan(Neg, Quiet, all1_payload)], || F32::from(-all1));
check_all_outcomes([F32::nan(Neg, Signaling, all1_payload)], || F32::from(-all1_snan));
// Intrinsics
let nan = F32::nan(Neg, Quiet, 0).as_f32();
let snan = F32::nan(Neg, Signaling, 1).as_f32();
check_all_outcomes([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)], || {
F32::from(f32::min(nan, nan))
});
check_all_outcomes([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)], || {
F32::from(nan.floor())
});
check_all_outcomes([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)], || F32::from(nan.sin()));
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(f32::min(nan, nan)),
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(nan.floor()),
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(nan.sin()),
);
check_all_outcomes(
HashSet::from_iter([
[
F32::nan(Pos, Quiet, 0),
F32::nan(Neg, Quiet, 0),
F32::nan(Pos, Quiet, 1),
@ -285,37 +283,32 @@ fn test_f32() {
F32::nan(Neg, Quiet, all1_payload),
F32::nan(Pos, Signaling, all1_payload),
F32::nan(Neg, Signaling, all1_payload),
]),
],
|| F32::from(just1.mul_add(F32::nan(Neg, Quiet, 2).as_f32(), all1_snan)),
);
check_all_outcomes([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)], || {
F32::from(nan.powf(nan))
});
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(nan.powf(nan)),
);
check_all_outcomes(
HashSet::from_iter([1.0f32.into()]),
[1.0f32.into()],
|| F32::from(1.0f32.powf(nan)), // special `pow` rule
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(nan.powi(1)),
);
check_all_outcomes([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)], || {
F32::from(nan.powi(1))
});
// libm functions
check_all_outcomes([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)], || {
F32::from(nan.sinh())
});
check_all_outcomes([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)], || {
F32::from(nan.atan2(nan))
});
check_all_outcomes([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)], || {
F32::from(nan.ln_gamma().0)
});
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(nan.sinh()),
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(nan.atan2(nan)),
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(nan.ln_gamma().0),
);
check_all_outcomes(
HashSet::from_iter([
[
F32::from(1.0),
F32::nan(Pos, Quiet, 0),
F32::nan(Neg, Quiet, 0),
@ -323,58 +316,57 @@ fn test_f32() {
F32::nan(Neg, Quiet, 1),
F32::nan(Pos, Signaling, 1),
F32::nan(Neg, Signaling, 1),
]),
],
|| F32::from(snan.powf(0.0)),
);
}
fn test_f64() {
// Freshly generated NaNs can have either sign.
check_all_outcomes(
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(0.0 / black_box(0.0)),
);
check_all_outcomes([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)], || {
F64::from(0.0 / black_box(0.0))
});
// When there are NaN inputs, their payload can be propagated, with any sign.
let all1_payload = u64_ones(51);
let all1 = F64::nan(Pos, Quiet, all1_payload).as_f64();
check_all_outcomes(
HashSet::from_iter([
[
F64::nan(Pos, Quiet, 0),
F64::nan(Neg, Quiet, 0),
F64::nan(Pos, Quiet, all1_payload),
F64::nan(Neg, Quiet, all1_payload),
]),
],
|| F64::from(0.0 + all1),
);
// When there are two NaN inputs, the output can be either one, or the preferred NaN.
let just1 = F64::nan(Neg, Quiet, 1).as_f64();
check_all_outcomes(
HashSet::from_iter([
[
F64::nan(Pos, Quiet, 0),
F64::nan(Neg, Quiet, 0),
F64::nan(Pos, Quiet, 1),
F64::nan(Neg, Quiet, 1),
F64::nan(Pos, Quiet, all1_payload),
F64::nan(Neg, Quiet, all1_payload),
]),
],
|| F64::from(just1 - all1),
);
// When there are *signaling* NaN inputs, they might be quieted or not.
let all1_snan = F64::nan(Pos, Signaling, all1_payload).as_f64();
check_all_outcomes(
HashSet::from_iter([
[
F64::nan(Pos, Quiet, 0),
F64::nan(Neg, Quiet, 0),
F64::nan(Pos, Quiet, all1_payload),
F64::nan(Neg, Quiet, all1_payload),
F64::nan(Pos, Signaling, all1_payload),
F64::nan(Neg, Signaling, all1_payload),
]),
],
|| F64::from(0.0 * all1_snan),
);
// Mix signaling and non-signaling NaN.
check_all_outcomes(
HashSet::from_iter([
[
F64::nan(Pos, Quiet, 0),
F64::nan(Neg, Quiet, 0),
F64::nan(Pos, Quiet, 1),
@ -383,27 +375,22 @@ fn test_f64() {
F64::nan(Neg, Quiet, all1_payload),
F64::nan(Pos, Signaling, all1_payload),
F64::nan(Neg, Signaling, all1_payload),
]),
],
|| F64::from(just1 % all1_snan),
);
// Intrinsics
let nan = F64::nan(Neg, Quiet, 0).as_f64();
let snan = F64::nan(Neg, Signaling, 1).as_f64();
check_all_outcomes([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)], || {
F64::from(f64::min(nan, nan))
});
check_all_outcomes([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)], || {
F64::from(nan.floor())
});
check_all_outcomes([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)], || F64::from(nan.sin()));
check_all_outcomes(
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(f64::min(nan, nan)),
);
check_all_outcomes(
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(nan.floor()),
);
check_all_outcomes(
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(nan.sin()),
);
check_all_outcomes(
HashSet::from_iter([
[
F64::nan(Pos, Quiet, 0),
F64::nan(Neg, Quiet, 0),
F64::nan(Pos, Quiet, 1),
@ -414,41 +401,35 @@ fn test_f64() {
F64::nan(Neg, Quiet, all1_payload),
F64::nan(Pos, Signaling, all1_payload),
F64::nan(Neg, Signaling, all1_payload),
]),
],
|| F64::from(just1.mul_add(F64::nan(Neg, Quiet, 2).as_f64(), all1_snan)),
);
check_all_outcomes([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)], || {
F64::from(nan.powf(nan))
});
check_all_outcomes(
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(nan.powf(nan)),
);
check_all_outcomes(
HashSet::from_iter([1.0f64.into()]),
[1.0f64.into()],
|| F64::from(1.0f64.powf(nan)), // special `pow` rule
);
check_all_outcomes(
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(nan.powi(1)),
);
check_all_outcomes([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)], || {
F64::from(nan.powi(1))
});
// libm functions
check_all_outcomes([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)], || {
F64::from(nan.sinh())
});
check_all_outcomes([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)], || {
F64::from(nan.atan2(nan))
});
check_all_outcomes([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)], || {
F64::from(ldexp(nan, 1))
});
check_all_outcomes([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)], || {
F64::from(nan.ln_gamma().0)
});
check_all_outcomes(
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(nan.sinh()),
);
check_all_outcomes(
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(nan.atan2(nan)),
);
check_all_outcomes(
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(ldexp(nan, 1)),
);
check_all_outcomes(
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(nan.ln_gamma().0),
);
check_all_outcomes(
HashSet::from_iter([
[
F64::from(1.0),
F64::nan(Pos, Quiet, 0),
F64::nan(Neg, Quiet, 0),
@ -456,7 +437,7 @@ fn test_f64() {
F64::nan(Neg, Quiet, 1),
F64::nan(Pos, Signaling, 1),
F64::nan(Neg, Signaling, 1),
]),
],
|| F64::from(snan.powf(0.0)),
);
}
@ -467,82 +448,79 @@ fn test_casts() {
let left1_payload_64 = (all1_payload_32 as u64) << (51 - 22);
// 64-to-32
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(F64::nan(Pos, Quiet, 0).as_f64() as f32),
);
check_all_outcomes([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)], || {
F32::from(F64::nan(Pos, Quiet, 0).as_f64() as f32)
});
// The preferred payload is always a possibility.
check_all_outcomes(
HashSet::from_iter([
[
F32::nan(Pos, Quiet, 0),
F32::nan(Neg, Quiet, 0),
F32::nan(Pos, Quiet, all1_payload_32),
F32::nan(Neg, Quiet, all1_payload_32),
]),
],
|| F32::from(F64::nan(Pos, Quiet, all1_payload_64).as_f64() as f32),
);
// If the input is signaling, then the output *may* also be signaling.
check_all_outcomes(
HashSet::from_iter([
[
F32::nan(Pos, Quiet, 0),
F32::nan(Neg, Quiet, 0),
F32::nan(Pos, Quiet, all1_payload_32),
F32::nan(Neg, Quiet, all1_payload_32),
F32::nan(Pos, Signaling, all1_payload_32),
F32::nan(Neg, Signaling, all1_payload_32),
]),
],
|| F32::from(F64::nan(Pos, Signaling, all1_payload_64).as_f64() as f32),
);
// Check that the low bits are gone (not the high bits).
check_all_outcomes([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)], || {
F32::from(F64::nan(Pos, Quiet, 1).as_f64() as f32)
});
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(F64::nan(Pos, Quiet, 1).as_f64() as f32),
);
check_all_outcomes(
HashSet::from_iter([
[
F32::nan(Pos, Quiet, 0),
F32::nan(Neg, Quiet, 0),
F32::nan(Pos, Quiet, 1),
F32::nan(Neg, Quiet, 1),
]),
],
|| F32::from(F64::nan(Pos, Quiet, 1 << (51 - 22)).as_f64() as f32),
);
check_all_outcomes(
HashSet::from_iter([
[
F32::nan(Pos, Quiet, 0),
F32::nan(Neg, Quiet, 0),
// The `1` payload becomes `0`, and the `0` payload cannot be signaling,
// so these are the only options.
]),
],
|| F32::from(F64::nan(Pos, Signaling, 1).as_f64() as f32),
);
// 32-to-64
check_all_outcomes(
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(F32::nan(Pos, Quiet, 0).as_f32() as f64),
);
check_all_outcomes([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)], || {
F64::from(F32::nan(Pos, Quiet, 0).as_f32() as f64)
});
// The preferred payload is always a possibility.
// Also checks that 0s are added on the right.
check_all_outcomes(
HashSet::from_iter([
[
F64::nan(Pos, Quiet, 0),
F64::nan(Neg, Quiet, 0),
F64::nan(Pos, Quiet, left1_payload_64),
F64::nan(Neg, Quiet, left1_payload_64),
]),
],
|| F64::from(F32::nan(Pos, Quiet, all1_payload_32).as_f32() as f64),
);
// If the input is signaling, then the output *may* also be signaling.
check_all_outcomes(
HashSet::from_iter([
[
F64::nan(Pos, Quiet, 0),
F64::nan(Neg, Quiet, 0),
F64::nan(Pos, Quiet, left1_payload_64),
F64::nan(Neg, Quiet, left1_payload_64),
F64::nan(Pos, Signaling, left1_payload_64),
F64::nan(Neg, Signaling, left1_payload_64),
]),
],
|| F64::from(F32::nan(Pos, Signaling, all1_payload_32).as_f32() as f64),
);
}
@ -552,48 +530,35 @@ fn test_simd() {
use std::simd::*;
let nan = F32::nan(Neg, Quiet, 0).as_f32();
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(unsafe { simd_div(f32x4::splat(0.0), f32x4::splat(0.0)) }[0]),
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(unsafe { simd_fmin(f32x4::splat(nan), f32x4::splat(nan)) }[0]),
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(unsafe { simd_fmax(f32x4::splat(nan), f32x4::splat(nan)) }[0]),
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| {
F32::from(
unsafe { simd_fma(f32x4::splat(nan), f32x4::splat(nan), f32x4::splat(nan)) }[0],
)
},
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(unsafe { simd_reduce_add_ordered::<_, f32>(f32x4::splat(nan), nan) }),
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(unsafe { simd_reduce_max::<_, f32>(f32x4::splat(nan)) }),
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(unsafe { simd_fsqrt(f32x4::splat(nan)) }[0]),
);
check_all_outcomes(
HashSet::from_iter([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)]),
|| F32::from(unsafe { simd_ceil(f32x4::splat(nan)) }[0]),
);
check_all_outcomes([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)], || {
F32::from(unsafe { simd_div(f32x4::splat(0.0), f32x4::splat(0.0)) }[0])
});
check_all_outcomes([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)], || {
F32::from(unsafe { simd_fmin(f32x4::splat(nan), f32x4::splat(nan)) }[0])
});
check_all_outcomes([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)], || {
F32::from(unsafe { simd_fmax(f32x4::splat(nan), f32x4::splat(nan)) }[0])
});
check_all_outcomes([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)], || {
F32::from(unsafe { simd_fma(f32x4::splat(nan), f32x4::splat(nan), f32x4::splat(nan)) }[0])
});
check_all_outcomes([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)], || {
F32::from(unsafe { simd_reduce_add_ordered::<_, f32>(f32x4::splat(nan), nan) })
});
check_all_outcomes([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)], || {
F32::from(unsafe { simd_reduce_max::<_, f32>(f32x4::splat(nan)) })
});
check_all_outcomes([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)], || {
F32::from(unsafe { simd_fsqrt(f32x4::splat(nan)) }[0])
});
check_all_outcomes([F32::nan(Pos, Quiet, 0), F32::nan(Neg, Quiet, 0)], || {
F32::from(unsafe { simd_ceil(f32x4::splat(nan)) }[0])
});
// Casts
check_all_outcomes(
HashSet::from_iter([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)]),
|| F64::from(unsafe { simd_cast::<f32x4, f64x4>(f32x4::splat(nan)) }[0]),
);
check_all_outcomes([F64::nan(Pos, Quiet, 0), F64::nan(Neg, Quiet, 0)], || {
F64::from(unsafe { simd_cast::<f32x4, f64x4>(f32x4::splat(nan)) }[0])
});
}
fn main() {