Skip to content

Commit

Permalink
add erf and erfc to nondet tests, and reduce how much we're changing …
Browse files Browse the repository at this point in the history
…the float test
  • Loading branch information
RalfJung committed Feb 16, 2025
1 parent f5330d0 commit 5c09010
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 125 deletions.
21 changes: 13 additions & 8 deletions src/intrinsics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let res = apply_random_float_error_ulp(
this,
res,
4
4, // log2(16)
);
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
Expand Down Expand Up @@ -289,7 +289,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let res = apply_random_float_error_ulp(
this,
res,
4
4, // log2(16)
);
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
Expand Down Expand Up @@ -411,7 +411,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// `binary_op` already called `generate_nan` if needed.
// Apply a relative error of 16ULP to simulate non-deterministic precision loss
// due to optimizations.
let res = apply_random_float_error_to_imm(this, res)?;
let res = apply_random_float_error_to_imm(this, res, 4 /* log2(16) */)?;
this.write_immediate(*res, dest)?;
}

Expand Down Expand Up @@ -464,7 +464,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
}
// Apply a relative error of 16ULP to simulate non-deterministic precision loss
// due to optimizations.
let res = apply_random_float_error_to_imm(this, res)?;
let res = apply_random_float_error_to_imm(this, res, 4 /* log2(16) */)?;
this.write_immediate(*res, dest)?;
}

Expand Down Expand Up @@ -503,13 +503,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
fn apply_random_float_error_to_imm<'tcx>(
ecx: &mut MiriInterpCx<'tcx>,
val: ImmTy<'tcx>,
ulp_exponent: u32,
) -> InterpResult<'tcx, ImmTy<'tcx>> {
let scalar = val.to_scalar_int()?;
let res: ScalarInt = match val.layout.ty.kind() {
ty::Float(FloatTy::F16) => apply_random_float_error_ulp(ecx, scalar.to_f16(), 4).into(),
ty::Float(FloatTy::F32) => apply_random_float_error_ulp(ecx, scalar.to_f32(), 4).into(),
ty::Float(FloatTy::F64) => apply_random_float_error_ulp(ecx, scalar.to_f64(), 4).into(),
ty::Float(FloatTy::F128) => apply_random_float_error_ulp(ecx, scalar.to_f128(), 4).into(),
ty::Float(FloatTy::F16) =>
apply_random_float_error_ulp(ecx, scalar.to_f16(), ulp_exponent).into(),
ty::Float(FloatTy::F32) =>
apply_random_float_error_ulp(ecx, scalar.to_f32(), ulp_exponent).into(),
ty::Float(FloatTy::F64) =>
apply_random_float_error_ulp(ecx, scalar.to_f64(), ulp_exponent).into(),
ty::Float(FloatTy::F128) =>
apply_random_float_error_ulp(ecx, scalar.to_f128(), ulp_exponent).into(),
_ => bug!("intrinsic called with non-float input type"),
};

Expand Down
14 changes: 8 additions & 6 deletions src/shims/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
let res = math::apply_random_float_error_ulp(
this,
res.to_soft(),
4
4, // log2(16)
);
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
Expand Down Expand Up @@ -799,7 +799,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
let res = math::apply_random_float_error_ulp(
this,
res,
4
4, // log2(16)
);
let res = this.adjust_nan(res, &[f1, f2]);
this.write_scalar(res, dest)?;
Expand Down Expand Up @@ -844,7 +844,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
let res = math::apply_random_float_error_ulp(
this,
res.to_soft(),
4
4, // log2(16)
);
let res = this.adjust_nan(res, &[f]);
this.write_scalar(res, dest)?;
Expand Down Expand Up @@ -873,7 +873,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
let res = math::apply_random_float_error_ulp(
this,
res,
4
4, // log2(16)
);
let res = this.adjust_nan(res, &[f1, f2]);
this.write_scalar(res, dest)?;
Expand Down Expand Up @@ -902,7 +902,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
this.write_int(sign, &signp)?;
// Apply a relative error of 16ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = math::apply_random_float_error_ulp(this, res.to_soft(), 4);
let res =
math::apply_random_float_error_ulp(this, res.to_soft(), 4 /* log2(16) */);
let res = this.adjust_nan(res, &[x]);
this.write_scalar(res, dest)?;
}
Expand All @@ -916,7 +917,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
this.write_int(sign, &signp)?;
// Apply a relative error of 16ULP to introduce some non-determinism
// simulating imprecise implementations and optimizations.
let res = math::apply_random_float_error_ulp(this, res.to_soft(), 4);
let res =
math::apply_random_float_error_ulp(this, res.to_soft(), 4 /* log2(16) */);
let res = this.adjust_nan(res, &[x]);
this.write_scalar(res, dest)?;
}
Expand Down
138 changes: 27 additions & 111 deletions tests/pass/float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,78 +13,27 @@ use std::fmt::{Debug, Display, LowerHex};
use std::hint::black_box;
use std::{f32, f64};

/// Another way of checking if 2 floating-point numbers are almost equal to eachother.
/// Using `a` and `b` as floating-point numbers:
/// Compare the two floats, allowing for $ulp many ULPs of error.
///
/// Instead of performing a simple EPSILON check (which we used at first):
/// The absolute difference between 'a' and 'b' must not be greater than some E (10^-6, ...)
///
/// We will now use ULP: `Units in the Last Place` or `Units of Least Precision`,
/// more specific, the difference in ULP of `a` and `b`.
/// First: The ULP of a float 'a' is the smallest possible change at 'a', so the ULP difference represents how
/// many discrete floating-point steps are needed to reach 'b' from 'a'.
///
/// ULP(a) is the distance between the 2 closest floating-point numbers `x` and `y` around `a`, satisfying x < a < y, x != y.
/// To use this to calculate the ULP difference we have to halve it (we need it at `a`, but we just went "up" and "down", halving it gives us this ULP).
/// Then take the difference of `b` and `a` and divide it by that ULP and finally round it.
/// We know now how many floating-point changes we have to apply to `a` to get to `b`.
///
/// So if this ULP difference is less than or equal to our chosen upper bound
/// we can say that `a` and `b` are approximately equal, because they lie "close" enough to each other to be considered equal.
///
/// Note: We can see that checking `a` and `b` with different signs has no meaning, but we should not forget
/// -0.0 and +0.0.
/// ULP means "Units in the Last Place" or "Units of Least Precision".
/// The ULP of a float `a`` is the smallest possible change at `a`, so the ULP difference represents how
/// many discrete floating-point steps are needed to reach the actual value from the expected value.
///
/// Essentially ULP can be seen as a distance metric of floating-point numbers, but with
/// the same amount of "spacing" between all consecutive representable values. So even though 2 very large floating point numbers
/// have a large value difference, their ULP can still be 1, so they are still "approximatly equal",
/// but the EPSILON check would have failed.
///
fn approx_eq_check<F: Float>(
actual: F,
expected: F,
allowed_ulp: F::Int,
) -> Result<(), NotApproxEq<F>>
where
F::Int: PartialOrd,
{
let actual_signum = actual.signum();
let expected_signum = expected.signum();

if actual_signum != expected_signum {
// Floats with different signs must both be 0.
if actual != expected {
return Err(NotApproxEq::SignsDiffer);
}
} else {
let ulp = (expected.next_up() - expected.next_down()).halve();
let ulp_diff = ((actual - expected) / ulp).round().as_int();

if ulp_diff > allowed_ulp {
return Err(NotApproxEq::UlpFail(ulp_diff));
}
}
Ok(())
}

/// Give more context to execution and result of [`approx_eq_check`].
enum NotApproxEq<F: Float> {
SignsDiffer,

/// Contains the actual ulp value calculated.
UlpFail(F::Int),
}

macro_rules! assert_approx_eq {
($a:expr, $b:expr, $ulp:expr) => {{
let (a, b) = ($a, $b);
let allowed_ulp = $ulp;
match approx_eq_check(a, b, allowed_ulp) {
Err(NotApproxEq::SignsDiffer) =>
panic!("{a:?} is not approximately equal to {b:?}: signs differ"),
Err(NotApproxEq::UlpFail(actual_ulp)) =>
panic!("{a:?} is not approximately equal to {b:?}\nulp diff: {actual_ulp} > {allowed_ulp}"),
Ok(_) => {}
let (actual, expected) = ($a, $b);
let allowed_ulp_diff = $ulp;
let _force_same_type = actual == expected;
// Approximate the ULP by taking half the distance between the number one place "up"
// and the number one place "down".
let ulp = (expected.next_up() - expected.next_down()) / 2.0;
let ulp_diff = ((actual - expected) / ulp).round() as i32;
if ulp_diff > allowed_ulp_diff {
panic!("{actual:?} is not approximately equal to {expected:?}\ndifference in ULP: {ulp_diff} > {allowed_ulp_diff}");
};
}};

Expand All @@ -101,23 +50,16 @@ fn main() {
ops();
nan_casts();
rounding();
libm();
mul_add();
libm();
test_fast();
test_algebraic();
test_fmuladd();
test_min_max_nondet();
test_non_determinism();
}

trait Float:
Copy
+ PartialEq
+ Debug
+ std::ops::Sub<Output = Self>
+ std::cmp::PartialOrd
+ std::ops::Div<Output = Self>
{
trait Float: Copy + PartialEq + Debug {
/// The unsigned integer with the same bit width as this float
type Int: Copy + PartialEq + LowerHex + Debug;
const BITS: u32 = size_of::<Self>() as u32 * 8;
Expand All @@ -131,15 +73,6 @@ trait Float:
const EXPONENT_BIAS: u32 = Self::EXPONENT_SAT >> 1;

fn to_bits(self) -> Self::Int;

// to make "approx_eq_check" generic
fn signum(self) -> Self;
fn next_up(self) -> Self;
fn next_down(self) -> Self;
fn round(self) -> Self;
// self / 2
fn halve(self) -> Self;
fn as_int(self) -> Self::Int;
}

macro_rules! impl_float {
Expand All @@ -152,27 +85,6 @@ macro_rules! impl_float {
fn to_bits(self) -> Self::Int {
self.to_bits()
}

fn signum(self) -> Self {
self.signum()
}
fn next_up(self) -> Self {
self.next_up()
}
fn next_down(self) -> Self {
self.next_down()
}
fn round(self) -> Self {
self.round()
}

fn halve(self) -> Self {
self / 2.0
}

fn as_int(self) -> Self::Int {
self as Self::Int
}
}
};
}
Expand Down Expand Up @@ -1117,8 +1029,8 @@ pub fn libm() {

#[allow(deprecated)]
{
assert_approx_eq!(5.0f32.abs_sub(3.0), 2.0f32);
assert_approx_eq!(3.0f64.abs_sub(5.0), 0.0f64);
assert_approx_eq!(5.0f32.abs_sub(3.0), 2.0);
assert_approx_eq!(3.0f64.abs_sub(5.0), 0.0);
}

assert_approx_eq!(27.0f32.cbrt(), 3.0f32);
Expand All @@ -1135,8 +1047,8 @@ pub fn libm() {

assert_approx_eq!(0f32.sin(), 0f32);
assert_approx_eq!((f64::consts::PI / 2f64).sin(), 1f64);
assert_approx_eq!(f32::consts::FRAC_PI_6.sin(), 0.5f32);
assert_approx_eq!(f64::consts::FRAC_PI_6.sin(), 0.5f64);
assert_approx_eq!(f32::consts::FRAC_PI_6.sin(), 0.5);
assert_approx_eq!(f64::consts::FRAC_PI_6.sin(), 0.5);
assert_approx_eq!(f32::consts::FRAC_PI_4.sin().asin(), f32::consts::FRAC_PI_4);
assert_approx_eq!(f64::consts::FRAC_PI_4.sin().asin(), f64::consts::FRAC_PI_4);

Expand All @@ -1147,8 +1059,8 @@ pub fn libm() {

assert_approx_eq!(0f32.cos(), 1f32);
assert_approx_eq!((f64::consts::PI * 2f64).cos(), 1f64);
assert_approx_eq!(f32::consts::FRAC_PI_3.cos(), 0.5f32);
assert_approx_eq!(f64::consts::FRAC_PI_3.cos(), 0.5f64);
assert_approx_eq!(f32::consts::FRAC_PI_3.cos(), 0.5);
assert_approx_eq!(f64::consts::FRAC_PI_3.cos(), 0.5);
assert_approx_eq!(f32::consts::FRAC_PI_4.cos().acos(), f32::consts::FRAC_PI_4);
assert_approx_eq!(f64::consts::FRAC_PI_4.cos().acos(), f64::consts::FRAC_PI_4);

Expand All @@ -1175,8 +1087,8 @@ pub fn libm() {
assert_approx_eq!(0.5f32.atanh(), 0.54930614433405484569762261846126285f32);
assert_approx_eq!(0.5f64.atanh(), 0.54930614433405484569762261846126285f64);

assert_approx_eq!(5.0f32.gamma(), 24.0f32);
assert_approx_eq!(5.0f64.gamma(), 24.0f64);
assert_approx_eq!(5.0f32.gamma(), 24.0);
assert_approx_eq!(5.0f64.gamma(), 24.0);
assert_approx_eq!((-0.5f32).gamma(), (-2.0) * f32::consts::PI.sqrt());
assert_approx_eq!((-0.5f64).gamma(), (-2.0) * f64::consts::PI.sqrt());

Expand Down Expand Up @@ -1424,6 +1336,8 @@ fn test_non_determinism() {
ensure_nondet(|| 1.0f32.atan2(2.0f32));
ensure_nondet(|| 0.5f32.atanh());
ensure_nondet(|| 5.0f32.gamma());
ensure_nondet(|| 5.0f32.erf());
ensure_nondet(|| 5.0f32.erfc());
}
pub fn test_operations_f64(a: f64, b: f64) {
test_operations_f!(a, b);
Expand All @@ -1446,6 +1360,8 @@ fn test_non_determinism() {
ensure_nondet(|| 1.0f64.tanh());
ensure_nondet(|| 0.5f64.atanh());
ensure_nondet(|| 5.0f64.gamma());
ensure_nondet(|| 5.0f64.erf());
ensure_nondet(|| 5.0f64.erfc());
}
pub fn test_operations_f128(a: f128, b: f128) {
test_operations_f!(a, b);
Expand Down

0 comments on commit 5c09010

Please sign in to comment.