about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRalf Jung <post@ralfj.de>2025-02-16 19:07:21 +0100
committerRalf Jung <post@ralfj.de>2025-02-16 19:24:31 +0100
commit1849256a267186949e922c657b15bb9f2e893498 (patch)
tree4fa028bebc8d768155a20a97a1d5454f525a8843
parent622e8f4812b8bcd9a3501ca7d299987fff733b68 (diff)
downloadrust-1849256a267186949e922c657b15bb9f2e893498.tar.gz
rust-1849256a267186949e922c657b15bb9f2e893498.zip
add erf and erfc to nondet tests, and reduce how much we're changing the float test
-rw-r--r--src/tools/miri/src/intrinsics/mod.rs21
-rw-r--r--src/tools/miri/src/shims/foreign_items.rs14
-rw-r--r--src/tools/miri/tests/pass/float.rs138
3 files changed, 48 insertions, 125 deletions
diff --git a/src/tools/miri/src/intrinsics/mod.rs b/src/tools/miri/src/intrinsics/mod.rs
index 377f3a902ed..a8655aee0ca 100644
--- a/src/tools/miri/src/intrinsics/mod.rs
+++ b/src/tools/miri/src/intrinsics/mod.rs
@@ -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)?;
@@ -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)?;
@@ -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)?;
             }
 
@@ -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)?;
             }
 
@@ -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"),
     };
 
diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs
index f7746ca81f2..ec8f6663822 100644
--- a/src/tools/miri/src/shims/foreign_items.rs
+++ b/src/tools/miri/src/shims/foreign_items.rs
@@ -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)?;
@@ -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)?;
@@ -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)?;
@@ -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)?;
@@ -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)?;
             }
@@ -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)?;
             }
diff --git a/src/tools/miri/tests/pass/float.rs b/src/tools/miri/tests/pass/float.rs
index 808495c73d3..0eb7d6e8309 100644
--- a/src/tools/miri/tests/pass/float.rs
+++ b/src/tools/miri/tests/pass/float.rs
@@ -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).abs().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}");
         };
     }};
 
@@ -101,8 +50,8 @@ fn main() {
     ops();
     nan_casts();
     rounding();
-    libm();
     mul_add();
+    libm();
     test_fast();
     test_algebraic();
     test_fmuladd();
@@ -110,14 +59,7 @@ fn main() {
     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;
@@ -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 {
@@ -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
-            }
         }
     };
 }
@@ -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);
@@ -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);
 
@@ -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);
 
@@ -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());
 
@@ -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);
@@ -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);