Skip to content

Commit ff22bff

Browse files
authored
ENH: Add floating point error metrics (#13)
1 parent 0574f6a commit ff22bff

File tree

2 files changed

+97
-0
lines changed

2 files changed

+97
-0
lines changed

include/xsf/config.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ XSF_HOST_DEVICE inline double asinh(double x) { return cuda::std::asinh(x); }
105105

106106
XSF_HOST_DEVICE inline bool signbit(double x) { return cuda::std::signbit(x); }
107107

108+
XSF_HOST_DEVICE inline double hypot(double x, double y) { return cuda::std::hypot(x, y); }
109+
108110
// Fallback to global namespace for functions unsupported on NVRTC
109111
#ifndef _LIBCUDACXX_COMPILER_NVRTC
110112
XSF_HOST_DEVICE inline double ceil(double x) { return cuda::std::ceil(x); }

include/xsf/fp_error_metrics.h

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#include "config.h"
2+
3+
namespace xsf {
4+
5+
template <typename T>
6+
XSF_HOST_DEVICE typename std::enable_if<std::is_floating_point<T>::value, T>::type extended_absolute_error(T actual, T desired) {
7+
if (actual == desired || std::isnan(actual) && std::isnan(desired)) {
8+
return T(0);
9+
}
10+
if (std::isnan(desired) || std::isnan(actual)) {
11+
/* If expected nan but got non-NaN or expected non-NaN but got NaN
12+
* we consider this to be an infinite error. */
13+
return std::numeric_limits<T>::infinity();
14+
}
15+
if (std::isinf(actual)) {
16+
/* We don't want to penalize early overflow too harshly, so instead
17+
* compare with the mythical value nextafter(max_float). */
18+
T sgn = std::copysign(1.0, actual);
19+
T max_float = std::numeric_limits<T>::max();
20+
// max_float * 2**-(mantissa_bits + 1) = ulp(max_float)
21+
T ulp = std::pow(2, -std::numeric_limits<T>::digits) * max_float;
22+
return std::abs((sgn * std::numeric_limits<T>::max() - desired) + sgn * ulp);
23+
}
24+
if (std::isinf(desired)) {
25+
T sgn = std::copysign(1.0, desired);
26+
T max_float = std::numeric_limits<T>::max();
27+
// max_float * 2**-(mantissa_bits + 1) = ulp(max_float)
28+
T ulp = std::pow(2, -std::numeric_limits<T>::digits) * max_float;
29+
return std::abs((sgn * std::numeric_limits<T>::max() - actual) + sgn * ulp);
30+
}
31+
return std::abs(actual - desired);
32+
}
33+
34+
35+
template <typename T>
36+
XSF_HOST_DEVICE T extended_absolute_error(std::complex<T> actual, std::complex<T> desired) {
37+
return std::hypot(extended_absolute_error(actual.real(), desired.real()), extended_absolute_error(actual.imag(), desired.imag()));
38+
}
39+
40+
41+
template <typename T>
42+
XSF_HOST_DEVICE typename std::enable_if<std::is_floating_point<T>::value, T>::type extended_relative_error(T actual, T desired) {
43+
T abs_error = extended_absolute_error(actual, desired);
44+
T abs_desired = std::abs(desired);
45+
if (desired == 0.0) {
46+
/* If the desired result is 0.0, normalize by smallest subnormal instead
47+
* of zero. */
48+
abs_desired = std::numeric_limits<T>::denorm_min();
49+
} else if (std::isinf(desired)) {
50+
abs_desired = std::numeric_limits<T>::max();
51+
} else if (std::isnan(desired)) {
52+
/* This ensures extended_relative_error(nan, nan) = 0 but
53+
* extended_relative_error(x0, x1) is infinite if one but not both of
54+
* x0 and x1 equals NaN */
55+
abs_desired = T(1);
56+
}
57+
return abs_error / abs_desired;
58+
}
59+
60+
61+
template <typename T>
62+
XSF_HOST_DEVICE T extended_relative_error(std::complex<T> actual, std::complex<T> desired) {
63+
T abs_error = extended_absolute_error(actual, desired);
64+
65+
if (desired.real() == 0.0) {
66+
desired.real(std::copysign(std::numeric_limits<T>::denorm_min(), desired.real()));
67+
} else if (std::isinf(desired.real())) {
68+
desired.real(std::copysign(std::numeric_limits<T>::max(), desired.real()));
69+
} else if (std::isnan(desired.real())) {
70+
/* In this case, the value used for desired doesn't matter. If desired.real() is NaN
71+
* but actual.real() isn't NaN, then the extended_absolute_error will be inf already
72+
* anyway. */
73+
desired.real(1.0);
74+
}
75+
76+
if (desired.imag() == 0.0) {
77+
desired.imag(std::copysign(std::numeric_limits<T>::denorm_min(), desired.imag()));
78+
} else if (std::isinf(desired.imag())) {
79+
desired.imag(std::copysign(std::numeric_limits<T>::max(), desired.imag()));
80+
} else if (std::isnan(desired.imag())) {
81+
/* In this case, the value used for desired doesn't matter. If desired.imag() is NaN
82+
* but actual.imag() isn't NaN, then the extended_absolute_error will be inf already
83+
* anyway. */
84+
desired.imag(1.0);
85+
}
86+
87+
if (!(std::isinf(desired.real()) || std::isinf(desired.imag())) && std::isinf(std::abs(desired))) {
88+
/* Rescale to avoid overflow */
89+
return (abs_error / 2.0) / (std::abs(desired / 2.0));
90+
}
91+
92+
return abs_error / abs(desired);
93+
}
94+
95+
}

0 commit comments

Comments
 (0)