Skip to content

Commit 67f5d3b

Browse files
committed
re-implement WithinUlp and Enable float16
1 parent f8ae141 commit 67f5d3b

3 files changed

Lines changed: 176 additions & 30 deletions

File tree

cpp/src/arrow/testing/gtest_util_test.cc

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
// under the License.
1717

1818
#include <cmath>
19+
#include <memory>
20+
#include <type_traits>
21+
#include <vector>
1922

2023
#include <gtest/gtest-spi.h>
2124
#include <gtest/gtest.h>
@@ -32,8 +35,10 @@
3235
#include "arrow/type.h"
3336
#include "arrow/type_traits.h"
3437
#include "arrow/util/checked_cast.h"
38+
#include "arrow/util/float16.h"
39+
#include "arrow/util/logger.h"
3540

36-
namespace arrow {
41+
namespace arrow::util {
3742

3843
// Test basic cases for contains NaN.
3944
class TestAssertContainsNaN : public ::testing::Test {};
@@ -198,8 +203,15 @@ void CheckWithinUlp(Float x, Float y, int n_ulp) {
198203
CheckWithinUlpSingle(-y, -x, n_ulp);
199204

200205
for (int exp : {1, -1, 10, -10}) {
201-
Float x_scaled = std::ldexp(x, exp);
202-
Float y_scaled = std::ldexp(y, exp);
206+
Float x_scaled(0);
207+
Float y_scaled(0);
208+
if constexpr (std::is_same_v<Float, Float16>) {
209+
x_scaled = Float16(std::ldexp(x.ToFloat(), exp));
210+
y_scaled = Float16(std::ldexp(y.ToFloat(), exp));
211+
} else {
212+
x_scaled = std::ldexp(x, exp);
213+
y_scaled = std::ldexp(y, exp);
214+
}
203215
CheckWithinUlpSingle(x_scaled, y_scaled, n_ulp);
204216
CheckWithinUlpSingle(y_scaled, x_scaled, n_ulp);
205217
}
@@ -219,8 +231,15 @@ void CheckNotWithinUlp(Float x, Float y, int n_ulp) {
219231
}
220232

221233
for (int exp : {1, -1, 10, -10}) {
222-
Float x_scaled = std::ldexp(x, exp);
223-
Float y_scaled = std::ldexp(y, exp);
234+
Float x_scaled(0);
235+
Float y_scaled(0);
236+
if constexpr (std::is_same_v<Float, Float16>) {
237+
x_scaled = Float16(std::ldexp(x.ToFloat(), exp));
238+
y_scaled = Float16(std::ldexp(y.ToFloat(), exp));
239+
} else {
240+
x_scaled = std::ldexp(x, exp);
241+
y_scaled = std::ldexp(y, exp);
242+
}
224243
CheckNotWithinUlpSingle(x_scaled, y_scaled, n_ulp);
225244
CheckNotWithinUlpSingle(y_scaled, x_scaled, n_ulp);
226245
}
@@ -242,6 +261,10 @@ TEST(TestWithinUlp, Double) {
242261
CheckWithinUlp(1.0, 0.9999999999999999, 1);
243262
CheckWithinUlp(1.0, 0.9999999999999988, 11);
244263
CheckNotWithinUlp(1.0, 0.9999999999999988, 10);
264+
CheckWithinUlp(1.0000000000000002, 0.9999999999999999, 2);
265+
CheckNotWithinUlp(1.0000000000000002, 0.9999999999999999, 1);
266+
CheckWithinUlp(0.9999999999999988, 1.0000000000000007, 14);
267+
CheckNotWithinUlp(0.9999999999999988, 1.0000000000000007, 13);
245268

246269
CheckWithinUlp(123.4567, 123.45670000000015, 11);
247270
CheckNotWithinUlp(123.4567, 123.45670000000015, 10);
@@ -271,6 +294,10 @@ TEST(TestWithinUlp, Float) {
271294
CheckWithinUlp(1.0f, 0.99999994f, 1);
272295
CheckWithinUlp(1.0f, 0.99999934f, 11);
273296
CheckNotWithinUlp(1.0f, 0.99999934f, 10);
297+
CheckWithinUlp(1.0000001f, 0.99999994f, 2);
298+
CheckNotWithinUlp(1.0000001f, 0.99999994f, 1);
299+
CheckWithinUlp(1.0000013f, 0.99999934f, 22);
300+
CheckNotWithinUlp(1.0000013f, 0.99999934f, 21);
274301

275302
CheckWithinUlp(123.456f, 123.456085f, 11);
276303
CheckNotWithinUlp(123.456f, 123.456085f, 10);
@@ -284,15 +311,65 @@ TEST(TestWithinUlp, Float) {
284311
CheckNotWithinUlp(12.34f, -12.34f, 10);
285312
}
286313

314+
std::vector<Float16> ConvertToFloat16Vector(const std::vector<float>& float_values) {
315+
std::vector<Float16> float16_vector;
316+
float16_vector.reserve(float_values.size());
317+
for (auto& value : float_values) {
318+
float16_vector.emplace_back(value);
319+
}
320+
return float16_vector;
321+
}
322+
323+
TEST(TestWithinUlp, Float16) {
324+
for (Float16 f : ConvertToFloat16Vector({0.0f, 1e-8f, 1.0f, 123.456f})) {
325+
CheckWithinUlp(f, f, 0);
326+
CheckWithinUlp(f, f, 1);
327+
CheckWithinUlp(f, f, 42);
328+
}
329+
CheckWithinUlp(Float16(-0.0f), Float16(0.0f), 1);
330+
CheckWithinUlp(Float16(1.0f), Float16(1.00097656f), 1);
331+
CheckWithinUlp(Float16(1.0f), Float16(1.01074219f), 11);
332+
CheckNotWithinUlp(Float16(1.0f), Float16(1.00097656f), 0);
333+
CheckNotWithinUlp(Float16(1.0f), Float16(1.01074219f), 10);
334+
// left and right have a different exponent but are still very close
335+
CheckWithinUlp(Float16(1.0f), Float16(0.999511719f), 1);
336+
CheckWithinUlp(Float16(1.0f), Float16(0.994628906f), 11);
337+
CheckNotWithinUlp(Float16(1.0f), Float16(0.994628906f), 10);
338+
CheckWithinUlp(Float16(1.00097656), Float16(0.999511719f), 2);
339+
CheckNotWithinUlp(Float16(1.00097656), Float16(0.999511719f), 1);
340+
CheckWithinUlp(Float16(1.01074219f), Float16(0.994628906f), 22);
341+
CheckNotWithinUlp(Float16(1.01074219f), Float16(0.994628906f), 21);
342+
343+
CheckWithinUlp(Float16(123.456f), Float16(124.143501f), 11);
344+
// The assertion below does not work because ldexp(Float16(124.143501f), 10)
345+
// results in inf in Float16.
346+
// CheckNotWithinUlp(Float16(123.456f), Float16(124.143501f), 10);
347+
348+
CheckWithinUlp(std::numeric_limits<Float16>::infinity(),
349+
std::numeric_limits<Float16>::infinity(), 10);
350+
CheckWithinUlp(-std::numeric_limits<Float16>::infinity(),
351+
-std::numeric_limits<Float16>::infinity(), 10);
352+
CheckWithinUlp(std::numeric_limits<Float16>::quiet_NaN(),
353+
std::numeric_limits<Float16>::quiet_NaN(), 10);
354+
CheckNotWithinUlp(std::numeric_limits<Float16>::infinity(),
355+
-std::numeric_limits<Float16>::infinity(), 10);
356+
CheckNotWithinUlp(Float16(12.34f), -std::numeric_limits<Float16>::infinity(), 10);
357+
CheckNotWithinUlp(Float16(12.34f), std::numeric_limits<Float16>::quiet_NaN(), 10);
358+
CheckNotWithinUlp(Float16(12.34f), Float16(-12.34f), 10);
359+
}
360+
287361
TEST(AssertTestWithinUlp, Basics) {
288362
AssertWithinUlp(123.4567, 123.45670000000015, 11);
289363
AssertWithinUlp(123.456f, 123.456085f, 11);
364+
AssertWithinUlp(Float16(123.456f), Float16(124.143501f), 11);
290365
#ifndef _WIN32
291366
// GH-47442
292367
EXPECT_FATAL_FAILURE(AssertWithinUlp(123.4567, 123.45670000000015, 10),
293368
"not within 10 ulps");
294369
EXPECT_FATAL_FAILURE(AssertWithinUlp(123.456f, 123.456085f, 10), "not within 10 ulps");
370+
EXPECT_FATAL_FAILURE(AssertWithinUlp(Float16(123.456f), Float16(124.143501f), 10),
371+
"not within 10 ulps");
295372
#endif
296373
}
297374

298-
} // namespace arrow
375+
} // namespace arrow::util

cpp/src/arrow/testing/math.cc

Lines changed: 85 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,53 +17,107 @@
1717

1818
#include "arrow/testing/math.h"
1919

20+
#include <algorithm>
2021
#include <cmath>
2122
#include <limits>
23+
#include <type_traits>
2224

2325
#include <gtest/gtest.h>
2426

27+
#include "arrow/util/float16.h"
2528
#include "arrow/util/logging_internal.h"
2629

2730
namespace arrow {
2831
namespace {
2932

3033
template <typename Float>
31-
bool WithinUlpOneWay(Float left, Float right, int n_ulps) {
32-
// The delta between 1.0 and the FP value immediately before it.
33-
// We're using this value because `frexp` returns a mantissa between 0.5 and 1.0.
34-
static const Float kOneUlp = Float(1.0) - std::nextafter(Float(1.0), Float(0.0));
34+
struct FloatToUInt;
3535

36-
DCHECK_GE(n_ulps, 1);
36+
template <>
37+
struct FloatToUInt<double> {
38+
using Type = uint64_t;
39+
};
3740

38-
if (left == 0) {
39-
return left == right;
41+
template <>
42+
struct FloatToUInt<float> {
43+
using Type = uint32_t;
44+
};
45+
46+
template <>
47+
struct FloatToUInt<util::Float16> {
48+
using Type = uint16_t;
49+
};
50+
51+
template <typename Float>
52+
struct UlpDistanceUtil {
53+
public:
54+
using UIntType = typename FloatToUInt<Float>::Type;
55+
static const UIntType kNumberOfBits = sizeof(Float) * 8;
56+
static const UIntType kSignMask = static_cast<UIntType>(1) << (kNumberOfBits - 1);
57+
58+
// This implementation is inspired by:
59+
// https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
60+
static UIntType UlpDistnace(Float left, Float right) {
61+
auto unsigned_left = BitCast<UIntType>(left);
62+
auto unsigned_right = BitCast<UIntType>(right);
63+
auto biased_left = ConvertSignAndMagnitudeToBiased(unsigned_left);
64+
auto biased_right = ConvertSignAndMagnitudeToBiased(unsigned_right);
65+
if (biased_left > biased_right) {
66+
std::swap(biased_left, biased_right);
67+
}
68+
return biased_right - biased_left;
4069
}
41-
if (left < 0) {
42-
left = -left;
43-
right = -right;
70+
71+
private:
72+
template <typename To, typename From>
73+
union BitCastUnion {
74+
explicit BitCastUnion(From from) : from(from) {}
75+
From from;
76+
To to;
77+
};
78+
79+
template <typename To, typename From>
80+
static UIntType BitCast(From value) {
81+
assert(sizeof(To) == sizeof(From));
82+
BitCastUnion<To, From> bit_cast(value);
83+
return bit_cast.to;
4484
}
4585

46-
int left_exp;
47-
Float left_mant = std::frexp(left, &left_exp);
48-
Float delta = static_cast<Float>(n_ulps) * kOneUlp;
49-
Float lower_bound = std::ldexp(left_mant - delta, left_exp);
50-
Float upper_bound = std::ldexp(left_mant + delta, left_exp);
51-
return right >= lower_bound && right <= upper_bound;
52-
}
86+
// Source reference (GoogleTest):
87+
// https://github.com/google/googletest/blob/085af2cc08600bdb13827ca40261abcbe5048bb5/googletest/include/gtest/internal/gtest-internal.h#L336-L342
88+
static UIntType ConvertSignAndMagnitudeToBiased(UIntType value) {
89+
if (value & kSignMask) {
90+
return ~value + 1;
91+
} else {
92+
return value | kSignMask;
93+
}
94+
}
95+
};
5396

5497
template <typename Float>
5598
bool WithinUlpGeneric(Float left, Float right, int n_ulps) {
56-
if (std::isnan(left) || std::isnan(right)) {
57-
return std::isnan(left) == std::isnan(right);
58-
}
59-
if (!std::isfinite(left) || !std::isfinite(right)) {
60-
return left == right;
99+
if constexpr (std::is_same_v<Float, util::Float16>) {
100+
if (left.is_nan() || right.is_nan()) {
101+
return left.is_nan() && right.is_nan();
102+
} else if (left.is_infinity() || right.is_infinity()) {
103+
return left == right;
104+
}
105+
} else {
106+
if (std::isnan(left) || std::isnan(right)) {
107+
return std::isnan(left) == std::isnan(right);
108+
}
109+
if (!std::isfinite(left) || !std::isfinite(right)) {
110+
return left == right;
111+
}
61112
}
113+
62114
if (n_ulps == 0) {
63115
return left == right;
64116
}
65-
return (std::abs(left) <= std::abs(right)) ? WithinUlpOneWay(left, right, n_ulps)
66-
: WithinUlpOneWay(right, left, n_ulps);
117+
118+
DCHECK_GE(n_ulps, 1);
119+
return UlpDistanceUtil<Float>::UlpDistnace(left, right) <=
120+
static_cast<uint64_t>(n_ulps);
67121
}
68122

69123
template <typename Float>
@@ -75,6 +129,10 @@ void AssertWithinUlpGeneric(Float left, Float right, int n_ulps) {
75129

76130
} // namespace
77131

132+
bool WithinUlp(util::Float16 left, util::Float16 right, int n_ulps) {
133+
return WithinUlpGeneric(left, right, n_ulps);
134+
}
135+
78136
bool WithinUlp(float left, float right, int n_ulps) {
79137
return WithinUlpGeneric(left, right, n_ulps);
80138
}
@@ -83,6 +141,9 @@ bool WithinUlp(double left, double right, int n_ulps) {
83141
return WithinUlpGeneric(left, right, n_ulps);
84142
}
85143

144+
void AssertWithinUlp(util::Float16 left, util::Float16 right, int n_ulps) {
145+
AssertWithinUlpGeneric(left, right, n_ulps);
146+
}
86147
void AssertWithinUlp(float left, float right, int n_ulps) {
87148
AssertWithinUlpGeneric(left, right, n_ulps);
88149
}

cpp/src/arrow/testing/math.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,19 @@
2121

2222
namespace arrow {
2323

24+
namespace util {
25+
class Float16;
26+
}
27+
28+
ARROW_TESTING_EXPORT
29+
bool WithinUlp(util::Float16 left, util::Float16 right, int n_ulps);
2430
ARROW_TESTING_EXPORT
2531
bool WithinUlp(float left, float right, int n_ulps);
2632
ARROW_TESTING_EXPORT
2733
bool WithinUlp(double left, double right, int n_ulps);
2834

35+
ARROW_TESTING_EXPORT
36+
void AssertWithinUlp(util::Float16 left, util::Float16 right, int n_ulps);
2937
ARROW_TESTING_EXPORT
3038
void AssertWithinUlp(float left, float right, int n_ulps);
3139
ARROW_TESTING_EXPORT

0 commit comments

Comments
 (0)