Skip to content

Commit 5c9eaa3

Browse files
committed
add erf and erfc to nondet tests, and reduce how much we're changing the float test
1 parent f5330d0 commit 5c9eaa3

File tree

3 files changed

+48
-125
lines changed

3 files changed

+48
-125
lines changed

src/intrinsics/mod.rs

+13-8
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
254254
let res = apply_random_float_error_ulp(
255255
this,
256256
res,
257-
4
257+
4, // log2(16)
258258
);
259259
let res = this.adjust_nan(res, &[f]);
260260
this.write_scalar(res, dest)?;
@@ -289,7 +289,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
289289
let res = apply_random_float_error_ulp(
290290
this,
291291
res,
292-
4
292+
4, // log2(16)
293293
);
294294
let res = this.adjust_nan(res, &[f]);
295295
this.write_scalar(res, dest)?;
@@ -411,7 +411,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
411411
// `binary_op` already called `generate_nan` if needed.
412412
// Apply a relative error of 16ULP to simulate non-deterministic precision loss
413413
// due to optimizations.
414-
let res = apply_random_float_error_to_imm(this, res)?;
414+
let res = apply_random_float_error_to_imm(this, res, 4 /* log2(16) */)?;
415415
this.write_immediate(*res, dest)?;
416416
}
417417

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

@@ -503,13 +503,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
503503
fn apply_random_float_error_to_imm<'tcx>(
504504
ecx: &mut MiriInterpCx<'tcx>,
505505
val: ImmTy<'tcx>,
506+
ulp_exponent: u32,
506507
) -> InterpResult<'tcx, ImmTy<'tcx>> {
507508
let scalar = val.to_scalar_int()?;
508509
let res: ScalarInt = match val.layout.ty.kind() {
509-
ty::Float(FloatTy::F16) => apply_random_float_error_ulp(ecx, scalar.to_f16(), 4).into(),
510-
ty::Float(FloatTy::F32) => apply_random_float_error_ulp(ecx, scalar.to_f32(), 4).into(),
511-
ty::Float(FloatTy::F64) => apply_random_float_error_ulp(ecx, scalar.to_f64(), 4).into(),
512-
ty::Float(FloatTy::F128) => apply_random_float_error_ulp(ecx, scalar.to_f128(), 4).into(),
510+
ty::Float(FloatTy::F16) =>
511+
apply_random_float_error_ulp(ecx, scalar.to_f16(), ulp_exponent).into(),
512+
ty::Float(FloatTy::F32) =>
513+
apply_random_float_error_ulp(ecx, scalar.to_f32(), ulp_exponent).into(),
514+
ty::Float(FloatTy::F64) =>
515+
apply_random_float_error_ulp(ecx, scalar.to_f64(), ulp_exponent).into(),
516+
ty::Float(FloatTy::F128) =>
517+
apply_random_float_error_ulp(ecx, scalar.to_f128(), ulp_exponent).into(),
513518
_ => bug!("intrinsic called with non-float input type"),
514519
};
515520

src/shims/foreign_items.rs

+8-6
Original file line numberDiff line numberDiff line change
@@ -770,7 +770,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
770770
let res = math::apply_random_float_error_ulp(
771771
this,
772772
res.to_soft(),
773-
4
773+
4, // log2(16)
774774
);
775775
let res = this.adjust_nan(res, &[f]);
776776
this.write_scalar(res, dest)?;
@@ -799,7 +799,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
799799
let res = math::apply_random_float_error_ulp(
800800
this,
801801
res,
802-
4
802+
4, // log2(16)
803803
);
804804
let res = this.adjust_nan(res, &[f1, f2]);
805805
this.write_scalar(res, dest)?;
@@ -844,7 +844,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
844844
let res = math::apply_random_float_error_ulp(
845845
this,
846846
res.to_soft(),
847-
4
847+
4, // log2(16)
848848
);
849849
let res = this.adjust_nan(res, &[f]);
850850
this.write_scalar(res, dest)?;
@@ -873,7 +873,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
873873
let res = math::apply_random_float_error_ulp(
874874
this,
875875
res,
876-
4
876+
4, // log2(16)
877877
);
878878
let res = this.adjust_nan(res, &[f1, f2]);
879879
this.write_scalar(res, dest)?;
@@ -902,7 +902,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
902902
this.write_int(sign, &signp)?;
903903
// Apply a relative error of 16ULP to introduce some non-determinism
904904
// simulating imprecise implementations and optimizations.
905-
let res = math::apply_random_float_error_ulp(this, res.to_soft(), 4);
905+
let res =
906+
math::apply_random_float_error_ulp(this, res.to_soft(), 4 /* log2(16) */);
906907
let res = this.adjust_nan(res, &[x]);
907908
this.write_scalar(res, dest)?;
908909
}
@@ -916,7 +917,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
916917
this.write_int(sign, &signp)?;
917918
// Apply a relative error of 16ULP to introduce some non-determinism
918919
// simulating imprecise implementations and optimizations.
919-
let res = math::apply_random_float_error_ulp(this, res.to_soft(), 4);
920+
let res =
921+
math::apply_random_float_error_ulp(this, res.to_soft(), 4 /* log2(16) */);
920922
let res = this.adjust_nan(res, &[x]);
921923
this.write_scalar(res, dest)?;
922924
}

tests/pass/float.rs

+27-111
Original file line numberDiff line numberDiff line change
@@ -13,78 +13,27 @@ use std::fmt::{Debug, Display, LowerHex};
1313
use std::hint::black_box;
1414
use std::{f32, f64};
1515

16-
/// Another way of checking if 2 floating-point numbers are almost equal to eachother.
17-
/// Using `a` and `b` as floating-point numbers:
16+
/// Compare the two floats, allowing for $ulp many ULPs of error.
1817
///
19-
/// Instead of performing a simple EPSILON check (which we used at first):
20-
/// The absolute difference between 'a' and 'b' must not be greater than some E (10^-6, ...)
21-
///
22-
/// We will now use ULP: `Units in the Last Place` or `Units of Least Precision`,
23-
/// more specific, the difference in ULP of `a` and `b`.
24-
/// First: The ULP of a float 'a' is the smallest possible change at 'a', so the ULP difference represents how
25-
/// many discrete floating-point steps are needed to reach 'b' from 'a'.
26-
///
27-
/// ULP(a) is the distance between the 2 closest floating-point numbers `x` and `y` around `a`, satisfying x < a < y, x != y.
28-
/// 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).
29-
/// Then take the difference of `b` and `a` and divide it by that ULP and finally round it.
30-
/// We know now how many floating-point changes we have to apply to `a` to get to `b`.
31-
///
32-
/// So if this ULP difference is less than or equal to our chosen upper bound
33-
/// we can say that `a` and `b` are approximately equal, because they lie "close" enough to each other to be considered equal.
34-
///
35-
/// Note: We can see that checking `a` and `b` with different signs has no meaning, but we should not forget
36-
/// -0.0 and +0.0.
18+
/// ULP means "Units in the Last Place" or "Units of Least Precision".
19+
/// The ULP of a float `a`` is the smallest possible change at `a`, so the ULP difference represents how
20+
/// many discrete floating-point steps are needed to reach the actual value from the expected value.
3721
///
3822
/// Essentially ULP can be seen as a distance metric of floating-point numbers, but with
3923
/// the same amount of "spacing" between all consecutive representable values. So even though 2 very large floating point numbers
4024
/// have a large value difference, their ULP can still be 1, so they are still "approximatly equal",
4125
/// but the EPSILON check would have failed.
42-
///
43-
fn approx_eq_check<F: Float>(
44-
actual: F,
45-
expected: F,
46-
allowed_ulp: F::Int,
47-
) -> Result<(), NotApproxEq<F>>
48-
where
49-
F::Int: PartialOrd,
50-
{
51-
let actual_signum = actual.signum();
52-
let expected_signum = expected.signum();
53-
54-
if actual_signum != expected_signum {
55-
// Floats with different signs must both be 0.
56-
if actual != expected {
57-
return Err(NotApproxEq::SignsDiffer);
58-
}
59-
} else {
60-
let ulp = (expected.next_up() - expected.next_down()).halve();
61-
let ulp_diff = ((actual - expected) / ulp).round().as_int();
62-
63-
if ulp_diff > allowed_ulp {
64-
return Err(NotApproxEq::UlpFail(ulp_diff));
65-
}
66-
}
67-
Ok(())
68-
}
69-
70-
/// Give more context to execution and result of [`approx_eq_check`].
71-
enum NotApproxEq<F: Float> {
72-
SignsDiffer,
73-
74-
/// Contains the actual ulp value calculated.
75-
UlpFail(F::Int),
76-
}
77-
7826
macro_rules! assert_approx_eq {
7927
($a:expr, $b:expr, $ulp:expr) => {{
80-
let (a, b) = ($a, $b);
81-
let allowed_ulp = $ulp;
82-
match approx_eq_check(a, b, allowed_ulp) {
83-
Err(NotApproxEq::SignsDiffer) =>
84-
panic!("{a:?} is not approximately equal to {b:?}: signs differ"),
85-
Err(NotApproxEq::UlpFail(actual_ulp)) =>
86-
panic!("{a:?} is not approximately equal to {b:?}\nulp diff: {actual_ulp} > {allowed_ulp}"),
87-
Ok(_) => {}
28+
let (actual, expected) = ($a, $b);
29+
let allowed_ulp_diff = $ulp;
30+
let _force_same_type = actual == expected;
31+
// Approximate the ULP by taking half the distance between the number one place "up"
32+
// and the number one place "down".
33+
let ulp = (expected.next_up() - expected.next_down()) / 2.0;
34+
let ulp_diff = ((actual - expected) / ulp).abs().round() as i32;
35+
if ulp_diff > allowed_ulp_diff {
36+
panic!("{actual:?} is not approximately equal to {expected:?}\ndifference in ULP: {ulp_diff} > {allowed_ulp_diff}");
8837
};
8938
}};
9039

@@ -101,23 +50,16 @@ fn main() {
10150
ops();
10251
nan_casts();
10352
rounding();
104-
libm();
10553
mul_add();
54+
libm();
10655
test_fast();
10756
test_algebraic();
10857
test_fmuladd();
10958
test_min_max_nondet();
11059
test_non_determinism();
11160
}
11261

113-
trait Float:
114-
Copy
115-
+ PartialEq
116-
+ Debug
117-
+ std::ops::Sub<Output = Self>
118-
+ std::cmp::PartialOrd
119-
+ std::ops::Div<Output = Self>
120-
{
62+
trait Float: Copy + PartialEq + Debug {
12163
/// The unsigned integer with the same bit width as this float
12264
type Int: Copy + PartialEq + LowerHex + Debug;
12365
const BITS: u32 = size_of::<Self>() as u32 * 8;
@@ -131,15 +73,6 @@ trait Float:
13173
const EXPONENT_BIAS: u32 = Self::EXPONENT_SAT >> 1;
13274

13375
fn to_bits(self) -> Self::Int;
134-
135-
// to make "approx_eq_check" generic
136-
fn signum(self) -> Self;
137-
fn next_up(self) -> Self;
138-
fn next_down(self) -> Self;
139-
fn round(self) -> Self;
140-
// self / 2
141-
fn halve(self) -> Self;
142-
fn as_int(self) -> Self::Int;
14376
}
14477

14578
macro_rules! impl_float {
@@ -152,27 +85,6 @@ macro_rules! impl_float {
15285
fn to_bits(self) -> Self::Int {
15386
self.to_bits()
15487
}
155-
156-
fn signum(self) -> Self {
157-
self.signum()
158-
}
159-
fn next_up(self) -> Self {
160-
self.next_up()
161-
}
162-
fn next_down(self) -> Self {
163-
self.next_down()
164-
}
165-
fn round(self) -> Self {
166-
self.round()
167-
}
168-
169-
fn halve(self) -> Self {
170-
self / 2.0
171-
}
172-
173-
fn as_int(self) -> Self::Int {
174-
self as Self::Int
175-
}
17688
}
17789
};
17890
}
@@ -1117,8 +1029,8 @@ pub fn libm() {
11171029

11181030
#[allow(deprecated)]
11191031
{
1120-
assert_approx_eq!(5.0f32.abs_sub(3.0), 2.0f32);
1121-
assert_approx_eq!(3.0f64.abs_sub(5.0), 0.0f64);
1032+
assert_approx_eq!(5.0f32.abs_sub(3.0), 2.0);
1033+
assert_approx_eq!(3.0f64.abs_sub(5.0), 0.0);
11221034
}
11231035

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

11361048
assert_approx_eq!(0f32.sin(), 0f32);
11371049
assert_approx_eq!((f64::consts::PI / 2f64).sin(), 1f64);
1138-
assert_approx_eq!(f32::consts::FRAC_PI_6.sin(), 0.5f32);
1139-
assert_approx_eq!(f64::consts::FRAC_PI_6.sin(), 0.5f64);
1050+
assert_approx_eq!(f32::consts::FRAC_PI_6.sin(), 0.5);
1051+
assert_approx_eq!(f64::consts::FRAC_PI_6.sin(), 0.5);
11401052
assert_approx_eq!(f32::consts::FRAC_PI_4.sin().asin(), f32::consts::FRAC_PI_4);
11411053
assert_approx_eq!(f64::consts::FRAC_PI_4.sin().asin(), f64::consts::FRAC_PI_4);
11421054

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

11481060
assert_approx_eq!(0f32.cos(), 1f32);
11491061
assert_approx_eq!((f64::consts::PI * 2f64).cos(), 1f64);
1150-
assert_approx_eq!(f32::consts::FRAC_PI_3.cos(), 0.5f32);
1151-
assert_approx_eq!(f64::consts::FRAC_PI_3.cos(), 0.5f64);
1062+
assert_approx_eq!(f32::consts::FRAC_PI_3.cos(), 0.5);
1063+
assert_approx_eq!(f64::consts::FRAC_PI_3.cos(), 0.5);
11521064
assert_approx_eq!(f32::consts::FRAC_PI_4.cos().acos(), f32::consts::FRAC_PI_4);
11531065
assert_approx_eq!(f64::consts::FRAC_PI_4.cos().acos(), f64::consts::FRAC_PI_4);
11541066

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

1178-
assert_approx_eq!(5.0f32.gamma(), 24.0f32);
1179-
assert_approx_eq!(5.0f64.gamma(), 24.0f64);
1090+
assert_approx_eq!(5.0f32.gamma(), 24.0);
1091+
assert_approx_eq!(5.0f64.gamma(), 24.0);
11801092
assert_approx_eq!((-0.5f32).gamma(), (-2.0) * f32::consts::PI.sqrt());
11811093
assert_approx_eq!((-0.5f64).gamma(), (-2.0) * f64::consts::PI.sqrt());
11821094

@@ -1424,6 +1336,8 @@ fn test_non_determinism() {
14241336
ensure_nondet(|| 1.0f32.atan2(2.0f32));
14251337
ensure_nondet(|| 0.5f32.atanh());
14261338
ensure_nondet(|| 5.0f32.gamma());
1339+
ensure_nondet(|| 5.0f32.erf());
1340+
ensure_nondet(|| 5.0f32.erfc());
14271341
}
14281342
pub fn test_operations_f64(a: f64, b: f64) {
14291343
test_operations_f!(a, b);
@@ -1446,6 +1360,8 @@ fn test_non_determinism() {
14461360
ensure_nondet(|| 1.0f64.tanh());
14471361
ensure_nondet(|| 0.5f64.atanh());
14481362
ensure_nondet(|| 5.0f64.gamma());
1363+
ensure_nondet(|| 5.0f64.erf());
1364+
ensure_nondet(|| 5.0f64.erfc());
14491365
}
14501366
pub fn test_operations_f128(a: f128, b: f128) {
14511367
test_operations_f!(a, b);

0 commit comments

Comments
 (0)