Skip to content

Commit c43c8d2

Browse files
committed
Check some float ops approximately
1 parent 323484c commit c43c8d2

File tree

5 files changed

+222
-4
lines changed

5 files changed

+222
-4
lines changed

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/std_float/tests/float.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,33 @@ macro_rules! unary_test {
1616
}
1717
}
1818

19-
macro_rules! binary_test {
19+
macro_rules! unary_approx_test {
2020
{ $scalar:tt, $($func:tt),+ } => {
2121
test_helpers::test_lanes! {
2222
$(
2323
fn $func<const LANES: usize>() {
24-
test_helpers::test_binary_elementwise(
24+
test_helpers::test_unary_elementwise_approx(
25+
&core_simd::simd::Simd::<$scalar, LANES>::$func,
26+
&$scalar::$func,
27+
&|_| true,
28+
8,
29+
)
30+
}
31+
)*
32+
}
33+
}
34+
}
35+
36+
macro_rules! binary_approx_test {
37+
{ $scalar:tt, $($func:tt),+ } => {
38+
test_helpers::test_lanes! {
39+
$(
40+
fn $func<const LANES: usize>() {
41+
test_helpers::test_binary_elementwise_approx(
2542
&core_simd::simd::Simd::<$scalar, LANES>::$func,
2643
&$scalar::$func,
2744
&|_, _| true,
45+
16,
2846
)
2947
}
3048
)*
@@ -53,10 +71,13 @@ macro_rules! impl_tests {
5371
mod $scalar {
5472
use std_float::StdFloat;
5573

56-
unary_test! { $scalar, sqrt, sin, cos, exp, exp2, ln, log2, log10, ceil, floor, round, trunc }
57-
binary_test! { $scalar, log }
74+
unary_test! { $scalar, sqrt, ceil, floor, round, trunc }
5875
ternary_test! { $scalar, mul_add }
5976

77+
// https://github.com/rust-lang/miri/issues/3555
78+
unary_approx_test! { $scalar, sin, cos, exp, exp2, ln, log2, log10 }
79+
binary_approx_test! { $scalar, log }
80+
6081
test_helpers::test_lanes! {
6182
fn fract<const LANES: usize>() {
6283
test_helpers::test_unary_elementwise_flush_subnormals(

crates/test_helpers/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ publish = false
66

77
[dependencies]
88
proptest = { version = "0.10", default-features = false, features = ["alloc"] }
9+
float-cmp = "0.10"
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//! Compare numeric types approximately.
2+
3+
use float_cmp::Ulps;
4+
5+
pub trait ApproxEq {
6+
fn approxeq(&self, other: &Self, _ulps: i64) -> bool;
7+
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result;
8+
}
9+
10+
impl ApproxEq for bool {
11+
fn approxeq(&self, other: &Self, _ulps: i64) -> bool {
12+
self == other
13+
}
14+
15+
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
16+
write!(f, "{:?}", self)
17+
}
18+
}
19+
20+
macro_rules! impl_integer_approxeq {
21+
{ $($type:ty),* } => {
22+
$(
23+
impl ApproxEq for $type {
24+
fn approxeq(&self, other: &Self, _ulps: i64) -> bool {
25+
self == other
26+
}
27+
28+
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
29+
write!(f, "{:?} ({:x})", self, self)
30+
}
31+
}
32+
)*
33+
};
34+
}
35+
36+
impl_integer_approxeq! { u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize }
37+
38+
macro_rules! impl_float_approxeq {
39+
{ $($type:ty),* } => {
40+
$(
41+
impl ApproxEq for $type {
42+
fn approxeq(&self, other: &Self, ulps: i64) -> bool {
43+
if self.is_nan() && other.is_nan() {
44+
true
45+
} else {
46+
(self.ulps(other) as i64).abs() <= ulps
47+
}
48+
}
49+
50+
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
51+
write!(f, "{:?} ({:x})", self, self.to_bits())
52+
}
53+
}
54+
)*
55+
};
56+
}
57+
58+
impl_float_approxeq! { f32, f64 }
59+
60+
impl<T: ApproxEq, const N: usize> ApproxEq for [T; N] {
61+
fn approxeq(&self, other: &Self, ulps: i64) -> bool {
62+
self.iter()
63+
.zip(other.iter())
64+
.fold(true, |value, (left, right)| {
65+
value && left.approxeq(right, ulps)
66+
})
67+
}
68+
69+
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
70+
#[repr(transparent)]
71+
struct Wrapper<'a, T: ApproxEq>(&'a T);
72+
73+
impl<T: ApproxEq> core::fmt::Debug for Wrapper<'_, T> {
74+
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
75+
self.0.fmt(f)
76+
}
77+
}
78+
79+
f.debug_list()
80+
.entries(self.iter().map(|x| Wrapper(x)))
81+
.finish()
82+
}
83+
}
84+
85+
#[doc(hidden)]
86+
pub struct ApproxEqWrapper<'a, T>(pub &'a T, pub i64);
87+
88+
impl<T: ApproxEq> PartialEq<T> for ApproxEqWrapper<'_, T> {
89+
fn eq(&self, other: &T) -> bool {
90+
self.0.approxeq(other, self.1)
91+
}
92+
}
93+
94+
impl<T: ApproxEq> core::fmt::Debug for ApproxEqWrapper<'_, T> {
95+
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
96+
self.0.fmt(f)
97+
}
98+
}
99+
100+
#[macro_export]
101+
macro_rules! prop_assert_approxeq {
102+
{ $a:expr, $b:expr, $ulps:expr $(,)? } => {
103+
{
104+
use $crate::approxeq::ApproxEqWrapper;
105+
let a = $a;
106+
let b = $b;
107+
proptest::prop_assert_eq!(ApproxEqWrapper(&a, $ulps), b);
108+
}
109+
};
110+
}

crates/test_helpers/src/lib.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ pub mod wasm;
1212
#[macro_use]
1313
pub mod biteq;
1414

15+
#[macro_use]
16+
pub mod approxeq;
17+
1518
pub mod subnormals;
1619
use subnormals::FlushSubnormals;
1720

@@ -185,6 +188,41 @@ pub fn test_unary_elementwise<Scalar, ScalarResult, Vector, VectorResult, const
185188
});
186189
}
187190

191+
/// Test a unary vector function against a unary scalar function, applied elementwise.
192+
///
193+
/// Floats are checked approximately.
194+
pub fn test_unary_elementwise_approx<
195+
Scalar,
196+
ScalarResult,
197+
Vector,
198+
VectorResult,
199+
const LANES: usize,
200+
>(
201+
fv: &dyn Fn(Vector) -> VectorResult,
202+
fs: &dyn Fn(Scalar) -> ScalarResult,
203+
check: &dyn Fn([Scalar; LANES]) -> bool,
204+
ulps: i64,
205+
) where
206+
Scalar: Copy + core::fmt::Debug + DefaultStrategy,
207+
ScalarResult: Copy + approxeq::ApproxEq + core::fmt::Debug + DefaultStrategy,
208+
Vector: Into<[Scalar; LANES]> + From<[Scalar; LANES]> + Copy,
209+
VectorResult: Into<[ScalarResult; LANES]> + From<[ScalarResult; LANES]> + Copy,
210+
{
211+
test_1(&|x: [Scalar; LANES]| {
212+
proptest::prop_assume!(check(x));
213+
let result_1: [ScalarResult; LANES] = fv(x.into()).into();
214+
let result_2: [ScalarResult; LANES] = x
215+
.iter()
216+
.copied()
217+
.map(fs)
218+
.collect::<Vec<_>>()
219+
.try_into()
220+
.unwrap();
221+
crate::prop_assert_approxeq!(result_1, result_2, ulps);
222+
Ok(())
223+
});
224+
}
225+
188226
/// Test a unary vector function against a unary scalar function, applied elementwise.
189227
///
190228
/// Where subnormals are flushed, use approximate equality.
@@ -290,6 +328,44 @@ pub fn test_binary_elementwise<
290328
});
291329
}
292330

331+
/// Test a binary vector function against a binary scalar function, applied elementwise.
332+
pub fn test_binary_elementwise_approx<
333+
Scalar1,
334+
Scalar2,
335+
ScalarResult,
336+
Vector1,
337+
Vector2,
338+
VectorResult,
339+
const LANES: usize,
340+
>(
341+
fv: &dyn Fn(Vector1, Vector2) -> VectorResult,
342+
fs: &dyn Fn(Scalar1, Scalar2) -> ScalarResult,
343+
check: &dyn Fn([Scalar1; LANES], [Scalar2; LANES]) -> bool,
344+
ulps: i64,
345+
) where
346+
Scalar1: Copy + core::fmt::Debug + DefaultStrategy,
347+
Scalar2: Copy + core::fmt::Debug + DefaultStrategy,
348+
ScalarResult: Copy + approxeq::ApproxEq + core::fmt::Debug + DefaultStrategy,
349+
Vector1: Into<[Scalar1; LANES]> + From<[Scalar1; LANES]> + Copy,
350+
Vector2: Into<[Scalar2; LANES]> + From<[Scalar2; LANES]> + Copy,
351+
VectorResult: Into<[ScalarResult; LANES]> + From<[ScalarResult; LANES]> + Copy,
352+
{
353+
test_2(&|x: [Scalar1; LANES], y: [Scalar2; LANES]| {
354+
proptest::prop_assume!(check(x, y));
355+
let result_1: [ScalarResult; LANES] = fv(x.into(), y.into()).into();
356+
let result_2: [ScalarResult; LANES] = x
357+
.iter()
358+
.copied()
359+
.zip(y.iter().copied())
360+
.map(|(x, y)| fs(x, y))
361+
.collect::<Vec<_>>()
362+
.try_into()
363+
.unwrap();
364+
crate::prop_assert_approxeq!(result_1, result_2, ulps);
365+
Ok(())
366+
});
367+
}
368+
293369
/// Test a binary vector function against a binary scalar function, applied elementwise.
294370
///
295371
/// Where subnormals are flushed, use approximate equality.

0 commit comments

Comments
 (0)