Skip to content

Commit e095b40

Browse files
committed
move assert_approx_eq back to being a macro-based check
1 parent c310216 commit e095b40

File tree

1 file changed

+13
-102
lines changed

1 file changed

+13
-102
lines changed

tests/pass/float.rs

+13-102
Original file line numberDiff line numberDiff line change
@@ -13,78 +13,26 @@ 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+
// Approximate the ULP by taking half the distance between the number one place "up"
31+
// and the number one place "down".
32+
let ulp = (expected.next_up() - expected.next_down()) / 2.0;
33+
let ulp_diff = ((actual - expected) / ulp).round() as i32;
34+
if ulp_diff > allowed_ulp_diff {
35+
panic!("{actual:?} is not approximately equal to {expected:?}\ndifference in ULP: {ulp_diff} > {allowed_ulp_diff}");
8836
};
8937
}};
9038

@@ -110,14 +58,7 @@ fn main() {
11058
test_non_determinism();
11159
}
11260

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-
{
61+
trait Float: Copy + PartialEq + Debug {
12162
/// The unsigned integer with the same bit width as this float
12263
type Int: Copy + PartialEq + LowerHex + Debug;
12364
const BITS: u32 = size_of::<Self>() as u32 * 8;
@@ -131,15 +72,6 @@ trait Float:
13172
const EXPONENT_BIAS: u32 = Self::EXPONENT_SAT >> 1;
13273

13374
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;
14375
}
14476

14577
macro_rules! impl_float {
@@ -152,27 +84,6 @@ macro_rules! impl_float {
15284
fn to_bits(self) -> Self::Int {
15385
self.to_bits()
15486
}
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-
}
17687
}
17788
};
17889
}

0 commit comments

Comments
 (0)