Skip to content

Commit c4e45c1

Browse files
authored
Merge pull request #38017 from mantidproject/ewm5044-floating-point-ornl
Add single source of truth for absolute and relative differences to `Mantid::Kernel::FloatingPointComparison` - `ornl-next`
2 parents b157e07 + c6cf184 commit c4e45c1

File tree

4 files changed

+224
-15
lines changed

4 files changed

+224
-15
lines changed

Framework/Kernel/inc/MantidKernel/FloatingPointComparison.h

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,18 @@
1414
namespace Mantid {
1515
namespace Kernel {
1616
/// Test for equality of doubles using compiler-defined precision
17-
template <typename T> MANTID_KERNEL_DLL bool equals(const T x, const T y);
17+
template <typename T> MANTID_KERNEL_DLL bool equals(T const x, T const y);
1818
/// Test whether x<=y within machine precision
19-
template <typename T> MANTID_KERNEL_DLL bool ltEquals(const T x, const T y);
19+
template <typename T> MANTID_KERNEL_DLL bool ltEquals(T const x, T const y);
2020
/// Test whether x>=y within machine precision
21-
template <typename T> MANTID_KERNEL_DLL bool gtEquals(const T x, const T y);
21+
template <typename T> MANTID_KERNEL_DLL bool gtEquals(T const x, T const y);
22+
/// Calculate absolute difference between x, y
23+
template <typename T> MANTID_KERNEL_DLL T absoluteDifference(T const x, T const y);
24+
/// Calculate relative difference between x, y
25+
template <typename T> MANTID_KERNEL_DLL T relativeDifference(T const x, T const y);
26+
/// Test whether x, y are within absolute tolerance tol
27+
template <typename T> MANTID_KERNEL_DLL bool withinAbsoluteDifference(T const x, T const y, T const tolerance);
28+
/// Test whether x, y are within relative tolerance tol
29+
template <typename T> MANTID_KERNEL_DLL bool withinRelativeDifference(T const x, T const y, T const tolerance);
2230
} // namespace Kernel
2331
} // namespace Mantid

Framework/Kernel/src/FloatingPointComparison.cpp

Lines changed: 98 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,9 @@ namespace Mantid::Kernel {
2121
* @param x :: LHS comparator
2222
* @param y :: RHS comparator
2323
* @returns True if the numbers are considered equal within the given tolerance,
24-
* false otherwise
24+
* false otherwise. False if any value is NaN.
2525
*/
26-
template <typename TYPE> bool equals(const TYPE x, const TYPE y) {
27-
return !(std::fabs(x - y) > std::numeric_limits<TYPE>::epsilon());
28-
}
26+
template <typename T> bool equals(T const x, T const y) { return std::abs(x - y) <= std::numeric_limits<T>::epsilon(); }
2927

3028
/**
3129
* Compare two floating-point numbers as to whether they satisfy x<=y within
@@ -35,7 +33,7 @@ template <typename TYPE> bool equals(const TYPE x, const TYPE y) {
3533
* @returns True if the numbers are considered <= within the machine tolerance,
3634
* false otherwise
3735
*/
38-
template <typename T> MANTID_KERNEL_DLL bool ltEquals(const T x, const T y) { return (equals(x, y) || x < y); }
36+
template <typename T> MANTID_KERNEL_DLL bool ltEquals(T const x, T const y) { return (equals(x, y) || x < y); }
3937

4038
/**
4139
* Compare two floating-point numbers as to whether they satisfy x>=y within
@@ -45,15 +43,103 @@ template <typename T> MANTID_KERNEL_DLL bool ltEquals(const T x, const T y) { re
4543
* @returns True if the numbers are considered <= within the machine tolerance,
4644
* false otherwise
4745
*/
48-
template <typename T> MANTID_KERNEL_DLL bool gtEquals(const T x, const T y) { return (equals(x, y) || x > y); }
46+
template <typename T> MANTID_KERNEL_DLL bool gtEquals(T const x, T const y) { return (equals(x, y) || x > y); }
47+
48+
/// ABSOLUTE AND RELATIVE DIFFERENCE
49+
50+
/**
51+
* Calculate the absolute difference of two floating-point numbers
52+
* @param x :: first value
53+
* @param y :: second value
54+
* @returns the value of the absolute difference
55+
*/
56+
template <typename T> T absoluteDifference(T const x, T const y) { return std::abs(x - y); }
57+
58+
/**
59+
* Calculate the relative difference of two floating-point numbers
60+
* @param x :: first value
61+
* @param y :: second value
62+
* @returns the value of the relative difference. Do NOT use this
63+
* to compare the result to a tolerance; for that, use withinRelativeDifference
64+
* instead, as it will be more efficient at the comparison.
65+
*/
66+
template <typename T> T relativeDifference(T const x, T const y) {
67+
// calculate numerator |x-y|
68+
T const num = absoluteDifference<T>(x, y);
69+
if (num <= std::numeric_limits<T>::epsilon()) {
70+
// if |x-y| == 0.0 (within machine tolerance), relative difference is zero
71+
return 0.0;
72+
} else {
73+
// otherwise we have to calculate the denominator
74+
T const denom = static_cast<T>(0.5 * (std::abs(x) + std::abs(y)));
75+
// NOTE if we made it this far, at least one of x or y is nonzero, so denom will be nonzero
76+
return num / denom;
77+
}
78+
}
79+
80+
/**
81+
* Compare floating point numbers for absolute difference to
82+
* within the given tolerance
83+
* @param x :: first value
84+
* @param y :: second value
85+
* @param tolerance :: the tolerance
86+
* @returns True if the numbers are considered equal within the given tolerance,
87+
* false otherwise. False if either value is NaN.
88+
*/
89+
template <typename T> bool withinAbsoluteDifference(T const x, T const y, T const tolerance) {
90+
return ltEquals(absoluteDifference<T>(x, y), tolerance);
91+
}
92+
93+
/**
94+
* Compare floating point numbers for relative difference to
95+
* within the given tolerance
96+
* @param x :: first value
97+
* @param y :: second value
98+
* @param tolerance :: the tolerance
99+
* @returns True if the numbers are considered equal within the given tolerance,
100+
* false otherwise. False if either value is NaN.
101+
*/
102+
template <typename T> bool withinRelativeDifference(T const x, T const y, T const tolerance) {
103+
// handle the case of NaNs
104+
if (std::isnan(x) || std::isnan(y)) {
105+
return false;
106+
}
107+
T const num = absoluteDifference<T>(x, y);
108+
if (!(num > std::numeric_limits<T>::epsilon())) {
109+
// if |x-y| == 0.0 (within machine tolerance), this test passes
110+
return true;
111+
} else {
112+
// otherwise we have to calculate the denominator
113+
T const denom = static_cast<T>(0.5 * (std::abs(x) + std::abs(y)));
114+
// if denom <= 1, then |x-y| > tol implies |x-y|/denom > tol, can return early
115+
// NOTE can only return early if BOTH denom > tol AND |x-y| > tol.
116+
if (denom <= 1. && !ltEquals(num, tolerance)) {
117+
return false;
118+
} else {
119+
// avoid division for improved performance
120+
return ltEquals(num, denom * tolerance);
121+
}
122+
}
123+
}
49124

50125
///@cond
51126
// Concrete instantiations
52-
template DLLExport bool equals<double>(const double, const double);
53-
template DLLExport bool equals<float>(const float, const float);
54-
template DLLExport bool ltEquals<double>(const double, const double);
55-
template DLLExport bool ltEquals<float>(const float, const float);
56-
template DLLExport bool gtEquals<double>(const double, const double);
57-
template DLLExport bool gtEquals<float>(const float, const float);
127+
template DLLExport bool equals<double>(double const, double const);
128+
template DLLExport bool equals<float>(float const, float const);
129+
template DLLExport bool ltEquals<double>(double const, double const);
130+
template DLLExport bool ltEquals<float>(float const, float const);
131+
template DLLExport bool gtEquals<double>(double const, double const);
132+
template DLLExport bool gtEquals<float>(float const, float const);
133+
//
134+
template DLLExport double absoluteDifference<double>(double const, double const);
135+
template DLLExport float absoluteDifference<float>(float const, float const);
136+
template DLLExport double relativeDifference<double>(double const, double const);
137+
template DLLExport float relativeDifference<float>(float const, float const);
138+
//
139+
template DLLExport bool withinAbsoluteDifference<double>(double const, double const, double const);
140+
template DLLExport bool withinAbsoluteDifference<float>(float const, float const, float const);
141+
template DLLExport bool withinRelativeDifference<double>(double const, double const, double const);
142+
template DLLExport bool withinRelativeDifference<float>(float const, float const, float const);
58143
///@endcond
144+
59145
} // namespace Mantid::Kernel

Framework/Kernel/test/FloatingPointComparisonTest.h

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,25 @@ class FloatingPointComparisonTest : public CxxTest::TestSuite {
3030
TS_ASSERT_EQUALS(Mantid::Kernel::equals(0.1, 1.0001 * tol), false);
3131
}
3232

33+
void test_with_NaN() {
34+
// everything compares false with an NaN
35+
constexpr double anan = std::numeric_limits<double>::quiet_NaN();
36+
constexpr double bnan = std::numeric_limits<double>::quiet_NaN();
37+
constexpr double real = 3.0;
38+
// equals
39+
TS_ASSERT_EQUALS(Mantid::Kernel::equals(anan, real), false);
40+
TS_ASSERT_EQUALS(Mantid::Kernel::equals(real, anan), false);
41+
TS_ASSERT_EQUALS(Mantid::Kernel::equals(anan, bnan), false);
42+
// ltEquals
43+
TS_ASSERT_EQUALS(Mantid::Kernel::ltEquals(anan, real), false);
44+
TS_ASSERT_EQUALS(Mantid::Kernel::ltEquals(real, anan), false);
45+
TS_ASSERT_EQUALS(Mantid::Kernel::ltEquals(anan, bnan), false);
46+
// gtEquals
47+
TS_ASSERT_EQUALS(Mantid::Kernel::gtEquals(anan, real), false);
48+
TS_ASSERT_EQUALS(Mantid::Kernel::gtEquals(real, anan), false);
49+
TS_ASSERT_EQUALS(Mantid::Kernel::gtEquals(anan, bnan), false);
50+
}
51+
3352
void test_LtEquals_With_X_Equal_To_Y_Produces_True() {
3453
TS_ASSERT_EQUALS(Mantid::Kernel::ltEquals(0.1, 0.1), true);
3554
TS_ASSERT_EQUALS(Mantid::Kernel::ltEquals(-0.1, -0.1), true);
@@ -63,4 +82,99 @@ class FloatingPointComparisonTest : public CxxTest::TestSuite {
6382
TS_ASSERT_EQUALS(Mantid::Kernel::gtEquals(-5.56, 0.23), false);
6483
TS_ASSERT_EQUALS(Mantid::Kernel::gtEquals(-0.00002, -0.00001), false);
6584
}
85+
86+
void test_absoluteDifference() {
87+
constexpr double left = 1.1, right = 1.0;
88+
// test value
89+
TS_ASSERT_EQUALS(Mantid::Kernel::absoluteDifference(left, right), std::abs(left - right));
90+
// test positive-definiteness
91+
TS_ASSERT_LESS_THAN(0.0, Mantid::Kernel::absoluteDifference(left, -right));
92+
TS_ASSERT_LESS_THAN(0.0, Mantid::Kernel::absoluteDifference(-left, right));
93+
TS_ASSERT_LESS_THAN(0.0, Mantid::Kernel::absoluteDifference(-left, -right));
94+
// test symmetry
95+
TS_ASSERT_EQUALS(Mantid::Kernel::absoluteDifference(left, right), Mantid::Kernel::absoluteDifference(right, left));
96+
// absolute difference with NaN is NaN
97+
constexpr double anan = std::numeric_limits<double>::quiet_NaN();
98+
constexpr double bnan = std::numeric_limits<double>::quiet_NaN();
99+
TS_ASSERT(std::isnan(Mantid::Kernel::absoluteDifference(left, anan)));
100+
TS_ASSERT(std::isnan(Mantid::Kernel::absoluteDifference(bnan, anan)));
101+
}
102+
103+
void test_relativeDifference() {
104+
constexpr double point3 = 0.3, notquitepoint3 = 0.2 + 0.1;
105+
TS_ASSERT_EQUALS(Mantid::Kernel::relativeDifference(point3, notquitepoint3), 0.0);
106+
TS_ASSERT_EQUALS(Mantid::Kernel::relativeDifference(2.3, 2.3), 0.0);
107+
TS_ASSERT_EQUALS(Mantid::Kernel::relativeDifference(2.3e208, 2.3e208), 0.0);
108+
// check no errors using zero
109+
TS_ASSERT_THROWS_NOTHING(Mantid::Kernel::relativeDifference(0.0, 0.0))
110+
TS_ASSERT(!std::isnan(Mantid::Kernel::relativeDifference(0.0, 0.0)));
111+
TS_ASSERT_EQUALS(Mantid::Kernel::relativeDifference(0.0, 0.0), 0.0);
112+
// check no errors using machine epsilon
113+
constexpr double realsmall = std::numeric_limits<double>::epsilon();
114+
TS_ASSERT_THROWS_NOTHING(Mantid::Kernel::relativeDifference(0.0, realsmall))
115+
TS_ASSERT(!std::isnan(Mantid::Kernel::relativeDifference(0.0, realsmall)));
116+
TS_ASSERT_EQUALS(Mantid::Kernel::relativeDifference(0.0, realsmall), 0.0);
117+
// check we get correct values for normal situations
118+
const double left = 2.6, right = 2.7;
119+
const double reldiff = 2.0 * std::abs(left - right) / (left + right);
120+
TS_ASSERT_EQUALS(Mantid::Kernel::relativeDifference(left, right), reldiff);
121+
TS_ASSERT_EQUALS(Mantid::Kernel::relativeDifference(right, left), reldiff);
122+
// relative difference with NaN is NaN
123+
constexpr double anan = std::numeric_limits<double>::quiet_NaN();
124+
constexpr double bnan = std::numeric_limits<double>::quiet_NaN();
125+
TS_ASSERT(std::isnan(Mantid::Kernel::relativeDifference(left, anan)));
126+
TS_ASSERT(std::isnan(Mantid::Kernel::absoluteDifference(bnan, anan)));
127+
}
128+
129+
void test_withinAbsoluteDifference() {
130+
TS_ASSERT_EQUALS(Mantid::Kernel::withinAbsoluteDifference(0.3, 0.2, 0.1), true);
131+
TS_ASSERT_EQUALS(Mantid::Kernel::withinAbsoluteDifference(0.3, 0.1, 0.1), false);
132+
TS_ASSERT_EQUALS(Mantid::Kernel::withinAbsoluteDifference(0.01, 0.011, 0.01), true);
133+
TS_ASSERT_EQUALS(Mantid::Kernel::withinAbsoluteDifference(0.01, -0.011, 0.01), false);
134+
TS_ASSERT_EQUALS(Mantid::Kernel::withinAbsoluteDifference(100.1, 100.15, 0.1), true);
135+
TS_ASSERT_EQUALS(Mantid::Kernel::withinAbsoluteDifference(12345678923456.789, 12345679023456.788, 0.0001), false);
136+
// case of NaNs -- nothing is close to an NaN
137+
constexpr double anan = std::numeric_limits<double>::quiet_NaN();
138+
constexpr double bnan = std::numeric_limits<double>::quiet_NaN();
139+
TS_ASSERT_EQUALS(Mantid::Kernel::withinAbsoluteDifference(anan, 0.3, 0.1), false);
140+
TS_ASSERT_EQUALS(Mantid::Kernel::withinAbsoluteDifference(anan, bnan, 0.1), false);
141+
}
142+
143+
void test_withinRelativeDifference() {
144+
// things difference at machine epsilon are equal
145+
const double point3 = 0.3, notquitepoint3 = 0.2 + 0.1;
146+
TS_ASSERT_EQUALS(Mantid::Kernel::withinRelativeDifference(point3, notquitepoint3, 1.e-307), true);
147+
// some cases with zero difference
148+
TS_ASSERT_EQUALS(Mantid::Kernel::withinRelativeDifference(2.3, 2.3, 1.e-307), true);
149+
TS_ASSERT_EQUALS(Mantid::Kernel::withinRelativeDifference(2.3e208, 2.3e208, 1.e-307), true);
150+
TS_ASSERT_EQUALS(Mantid::Kernel::withinRelativeDifference(2.3e-208, 2.3e-208, 0.0), true);
151+
// case of large magnitude values -- even though abs diff would always fail, rel diff can still pass
152+
// - passing
153+
TS_ASSERT_EQUALS(Mantid::Kernel::withinAbsoluteDifference(2.31e208, 2.32e208, 0.01), false);
154+
TS_ASSERT_EQUALS(Mantid::Kernel::withinRelativeDifference(2.31e208, 2.32e208, 0.01), true);
155+
// - failing
156+
TS_ASSERT_EQUALS(Mantid::Kernel::withinRelativeDifference(2.3e208, 2.4e208, 0.01), false);
157+
// case of small magnitude values -- even though abs diff would always pass, rel diff still can fail
158+
// - passing
159+
TS_ASSERT_EQUALS(Mantid::Kernel::withinAbsoluteDifference(2.31e-10, 2.32e-10, 0.01), true);
160+
TS_ASSERT_EQUALS(Mantid::Kernel::withinRelativeDifference(2.31e-10, 2.32e-10, 0.01), true);
161+
// - failing
162+
TS_ASSERT_EQUALS(Mantid::Kernel::withinAbsoluteDifference(2.3e-10, 2.4e-10, 0.01), true);
163+
TS_ASSERT_EQUALS(Mantid::Kernel::withinRelativeDifference(2.3e-10, 2.4e-10, 0.01), false);
164+
// case of normal-sized values
165+
const double left = 2.6, right = 2.7, far = 3.0;
166+
const double reldiff = 2.0 * std::abs(left - right) / (left + right);
167+
const double tolerance = 1.01 * reldiff;
168+
// - passing
169+
TS_ASSERT_EQUALS(Mantid::Kernel::withinRelativeDifference(left, right, tolerance), true);
170+
TS_ASSERT_EQUALS(Mantid::Kernel::withinRelativeDifference(right, left, tolerance), true);
171+
// - failing
172+
TS_ASSERT_EQUALS(Mantid::Kernel::withinRelativeDifference(left, far, tolerance), false);
173+
TS_ASSERT_EQUALS(Mantid::Kernel::withinRelativeDifference(far, left, tolerance), false);
174+
// case of NaNs -- nothing is close to an NaN
175+
constexpr double anan = std::numeric_limits<double>::quiet_NaN();
176+
constexpr double bnan = std::numeric_limits<double>::quiet_NaN();
177+
TS_ASSERT_EQUALS(Mantid::Kernel::withinRelativeDifference(anan, 0.3, 0.1), false);
178+
TS_ASSERT_EQUALS(Mantid::Kernel::withinRelativeDifference(anan, bnan, 0.1), false);
179+
}
66180
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- add new functions for efficiently calculating absolute and relative differences

0 commit comments

Comments
 (0)