This is an automated email from the ASF dual-hosted git repository.
kou pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git
The following commit(s) were added to refs/heads/main by this push:
new 4bf9a16f7a GH-48408: [C++] Enable ULP-based float comparison (#49290)
4bf9a16f7a is described below
commit 4bf9a16f7a2cee7700698aa135287000dbf41a4a
Author: Arash Andishgar <[email protected]>
AuthorDate: Wed Jun 17 00:59:03 2026 +0330
GH-48408: [C++] Enable ULP-based float comparison (#49290)
### Rationale for this change
Enable ULP-based floating-point comparison.
### What changes are included in this PR?
Add `arrow::EqualOptions::use_ulp_distance` and
`arrow::EqualOptions::ulp_distance`.
### Are these changes tested?
Yes.
### Are there any user-facing changes?
Yes. The ULP-based comparison method is enabled via
`arrow::EqualOptions::use_ulp_distance` and `arrow::EqualOptions::ulp_distance`.
* GitHub Issue: #48408
Lead-authored-by: arash andishgar <[email protected]>
Co-authored-by: Antoine Pitrou <[email protected]>
Signed-off-by: Sutou Kouhei <[email protected]>
---
c_glib/arrow-glib/basic-array.cpp | 5 +-
cpp/src/arrow/CMakeLists.txt | 1 +
cpp/src/arrow/array/array_test.cc | 52 ++++-
cpp/src/arrow/array/statistics_test.cc | 6 +-
cpp/src/arrow/chunked_array.cc | 6 +-
cpp/src/arrow/chunked_array.h | 4 +
cpp/src/arrow/chunked_array_test.cc | 4 +
cpp/src/arrow/compare.cc | 258 +++++++++++----------
cpp/src/arrow/compare.h | 49 +++-
cpp/src/arrow/meson.build | 1 +
cpp/src/arrow/record_batch.h | 9 +-
cpp/src/arrow/record_batch_test.cc | 6 +-
cpp/src/arrow/scalar_test.cc | 89 ++++++-
cpp/src/arrow/table_test.cc | 14 +-
cpp/src/arrow/testing/gtest_util_test.cc | 184 +--------------
cpp/src/arrow/testing/math.cc | 84 +------
cpp/src/arrow/testing/math.h | 13 +-
cpp/src/arrow/util/CMakeLists.txt | 1 +
cpp/src/arrow/util/meson.build | 1 +
.../{testing/math.cc => util/ulp_distance.cc} | 62 +----
.../math.h => util/ulp_distance_internal.h} | 25 +-
.../ulp_distance_test.cc} | 179 +-------------
22 files changed, 400 insertions(+), 653 deletions(-)
diff --git a/c_glib/arrow-glib/basic-array.cpp
b/c_glib/arrow-glib/basic-array.cpp
index bf5bf60d00..45ff4c1cae 100644
--- a/c_glib/arrow-glib/basic-array.cpp
+++ b/c_glib/arrow-glib/basic-array.cpp
@@ -279,7 +279,8 @@ garrow_equal_options_get_property(GObject *object,
g_value_set_boolean(value, priv->options.nans_equal());
break;
case PROP_ABSOLUTE_TOLERANCE:
- g_value_set_double(value, priv->options.atol());
+ g_value_set_double(value,
+ priv->options.atol().has_value() ?
*priv->options.atol() : 0.0);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
@@ -348,7 +349,7 @@ garrow_equal_options_class_init(GArrowEqualOptionsClass
*klass)
"of floating-point values",
-G_MAXDOUBLE,
G_MAXDOUBLE,
- options.atol(),
+ options.atol().has_value() ? *options.atol() :
0.0,
static_cast<GParamFlags>(G_PARAM_READWRITE));
g_object_class_install_property(gobject_class, PROP_ABSOLUTE_TOLERANCE,
spec);
}
diff --git a/cpp/src/arrow/CMakeLists.txt b/cpp/src/arrow/CMakeLists.txt
index 453a06f9a8..8750598f6c 100644
--- a/cpp/src/arrow/CMakeLists.txt
+++ b/cpp/src/arrow/CMakeLists.txt
@@ -576,6 +576,7 @@ set(ARROW_UTIL_SRCS
util/time.cc
util/tracing.cc
util/trie.cc
+ util/ulp_distance.cc
util/union_util.cc
util/unreachable.cc
util/uri.cc
diff --git a/cpp/src/arrow/array/array_test.cc
b/cpp/src/arrow/array/array_test.cc
index 64ea3fd71a..dcfe1c76c3 100644
--- a/cpp/src/arrow/array/array_test.cc
+++ b/cpp/src/arrow/array/array_test.cc
@@ -2199,11 +2199,19 @@ void CheckFloatApproxEqualsWithAtol() {
auto options = EqualOptions::Defaults().atol(0.2);
ASSERT_FALSE(a->Equals(b));
+ ASSERT_TRUE(a->Equals(b, options));
+ ARROW_SUPPRESS_DEPRECATION_WARNING
ASSERT_TRUE(a->Equals(b, options.use_atol(true)));
+ ASSERT_FALSE(a->Equals(b, options.use_atol(false)));
+ ARROW_UNSUPPRESS_DEPRECATION_WARNING
ASSERT_TRUE(a->ApproxEquals(b, options));
- ASSERT_FALSE(a->RangeEquals(0, 1, 0, b, options));
+ ASSERT_FALSE(a->RangeEquals(0, 1, 0, b));
+ ASSERT_TRUE(a->RangeEquals(0, 1, 0, b, options));
+ ARROW_SUPPRESS_DEPRECATION_WARNING
ASSERT_TRUE(a->RangeEquals(0, 1, 0, b, options.use_atol(true)));
+ ASSERT_FALSE(a->RangeEquals(0, 1, 0, b, options.use_atol(false)));
+ ARROW_UNSUPPRESS_DEPRECATION_WARNING
ASSERT_TRUE(ArrayRangeApproxEquals(*a, *b, 0, 1, 0, options));
}
@@ -2413,6 +2421,42 @@ void CheckFloatingZeroEquality() {
}
}
+template <typename TYPE>
+void CheckFloatApproxEqualsWithUlpDistance() {
+ using CType =
+ std::conditional_t<is_half_float_type<TYPE>::value, Float16, typename
TYPE::c_type>;
+ auto type = TypeTraits<TYPE>::type_singleton();
+ std::vector<CType> a, b;
+ a = {CType(NAN), CType(+0.0), CType(INFINITY)};
+ b = {CType(NAN), CType(-0.0), CType(INFINITY)};
+ if constexpr (is_half_float_type<TYPE>::value) {
+ a.push_back(Float16(1.00097656));
+ b.push_back(Float16(0.999511719f));
+ } else if constexpr (std::is_same_v<TYPE, DoubleType>) {
+ a.push_back(CType(0.9999999999999999));
+ b.push_back(CType(1.0000000000000002));
+ } else if constexpr (std::is_same_v<TYPE, FloatType>) {
+ a.push_back(CType(1.0000001f));
+ b.push_back(CType(0.99999994f));
+ }
+
+ std::shared_ptr<Array> array_a, array_b;
+ ArrayFromVector<TYPE>(type, a, &array_a);
+ ArrayFromVector<TYPE>(type, b, &array_b);
+ auto options = EqualOptions::Defaults().ulp_distance(2);
+
+ // Check with NaN
+ ASSERT_FALSE(array_a->Equals(array_b, options));
+ ASSERT_TRUE(array_a->Equals(array_b, options.nans_equal(true)));
+
+ // Check With Signed Zero
+ ASSERT_FALSE(
+ array_a->Equals(array_b,
options.nans_equal(true).signed_zeros_equal(false)));
+
+ // Check with Ulp Distance
+ ASSERT_FALSE(array_a->Equals(array_b,
options.nans_equal(true).ulp_distance(1)));
+}
+
TEST(TestPrimitiveAdHoc, FloatingApproxEquals) {
CheckApproxEquals<FloatType>();
CheckApproxEquals<DoubleType>();
@@ -2446,6 +2490,12 @@ TEST(TestPrimitiveAdHoc, FloatingZeroEquality) {
CheckFloatingZeroEquality<HalfFloatType>();
}
+TEST(TestPrimitiveAdHoc, FloatingUlpDistanceEquality) {
+ CheckFloatApproxEqualsWithUlpDistance<HalfFloatType>();
+ CheckFloatApproxEqualsWithUlpDistance<FloatType>();
+ CheckFloatApproxEqualsWithUlpDistance<DoubleType>();
+}
+
// ----------------------------------------------------------------------
// FixedSizeBinary tests
diff --git a/cpp/src/arrow/array/statistics_test.cc
b/cpp/src/arrow/array/statistics_test.cc
index 62e1ddf831..45535ce823 100644
--- a/cpp/src/arrow/array/statistics_test.cc
+++ b/cpp/src/arrow/array/statistics_test.cc
@@ -255,8 +255,12 @@ TEST_F(TestArrayStatisticsEqualityDoubleValue, NaN) {
TEST_F(TestArrayStatisticsEqualityDoubleValue, ApproximateEquals) {
statistics1_.max = 0.5001f;
statistics2_.max = 0.5;
- ASSERT_FALSE(statistics1_.Equals(statistics2_, options_.atol(1e-3)));
+ ASSERT_FALSE(statistics1_.Equals(statistics2_, options_));
+ ASSERT_TRUE(statistics1_.Equals(statistics2_, options_.atol(1e-3)));
+ ARROW_SUPPRESS_DEPRECATION_WARNING
ASSERT_TRUE(statistics1_.Equals(statistics2_,
options_.atol(1e-3).use_atol(true)));
+ ASSERT_FALSE(statistics1_.Equals(statistics2_,
options_.atol(1e-3).use_atol(false)));
+ ARROW_UNSUPPRESS_DEPRECATION_WARNING
}
} // namespace arrow
diff --git a/cpp/src/arrow/chunked_array.cc b/cpp/src/arrow/chunked_array.cc
index 0fa174c175..edf23e970d 100644
--- a/cpp/src/arrow/chunked_array.cc
+++ b/cpp/src/arrow/chunked_array.cc
@@ -161,7 +161,11 @@ bool ChunkedArray::Equals(const
std::shared_ptr<ChunkedArray>& other,
bool ChunkedArray::ApproxEquals(const ChunkedArray& other,
const EqualOptions& equal_options) const {
- return Equals(other, equal_options.use_atol(true));
+ auto resolved_options = equal_options;
+ if (!resolved_options.atol()) {
+ resolved_options = resolved_options.atol(kDefaultAbsoluteTolerance);
+ }
+ return Equals(other, resolved_options);
}
Result<std::shared_ptr<Scalar>> ChunkedArray::GetScalar(int64_t index) const {
diff --git a/cpp/src/arrow/chunked_array.h b/cpp/src/arrow/chunked_array.h
index 02bcd0f902..2b581d0bb6 100644
--- a/cpp/src/arrow/chunked_array.h
+++ b/cpp/src/arrow/chunked_array.h
@@ -166,6 +166,10 @@ class ARROW_EXPORT ChunkedArray {
bool Equals(const std::shared_ptr<ChunkedArray>& other,
const EqualOptions& opts = EqualOptions::Defaults()) const;
/// \brief Determine if two chunked arrays approximately equal
+ ///
+ /// If the absolute tolerance (atol) is not specified in \ref
arrow::EqualOptions,
+ /// 'arrow::kDefaultAbsoluteTolerance' is used.
+ ///
bool ApproxEquals(const ChunkedArray& other,
const EqualOptions& = EqualOptions::Defaults()) const;
diff --git a/cpp/src/arrow/chunked_array_test.cc
b/cpp/src/arrow/chunked_array_test.cc
index 326eb24d08..90b90a731b 100644
--- a/cpp/src/arrow/chunked_array_test.cc
+++ b/cpp/src/arrow/chunked_array_test.cc
@@ -202,7 +202,11 @@ TEST_F(TestChunkedArray, ApproxEquals) {
auto options = EqualOptions::Defaults().atol(1e-3);
ASSERT_FALSE(chunked_array_1->Equals(chunked_array_2));
+ ASSERT_TRUE(chunked_array_1->Equals(chunked_array_2, options));
+ ARROW_SUPPRESS_DEPRECATION_WARNING
ASSERT_TRUE(chunked_array_1->Equals(chunked_array_2,
options.use_atol(true)));
+ ASSERT_FALSE(chunked_array_1->Equals(chunked_array_2,
options.use_atol(false)));
+ ARROW_UNSUPPRESS_DEPRECATION_WARNING
ASSERT_TRUE(chunked_array_1->ApproxEquals(*chunked_array_2, options));
}
diff --git a/cpp/src/arrow/compare.cc b/cpp/src/arrow/compare.cc
index 26f56d9b58..06f1d14e73 100644
--- a/cpp/src/arrow/compare.cc
+++ b/cpp/src/arrow/compare.cc
@@ -53,6 +53,7 @@
#include "arrow/util/macros.h"
#include "arrow/util/memory_internal.h"
#include "arrow/util/ree_util.h"
+#include "arrow/util/ulp_distance_internal.h"
#include "arrow/util/unreachable.h"
#include "arrow/visit_scalar_inline.h"
#include "arrow/visit_type_inline.h"
@@ -71,39 +72,53 @@ using util::Float16;
namespace {
-template <bool Approximate, bool NansEqual, bool SignedZerosEqual>
+template <bool AbsoluteTolerance, bool NansEqual, bool SignedZerosEqual,
+ bool UlpDistanceEqual>
struct FloatingEqualityFlags {
- static constexpr bool approximate = Approximate;
+ static constexpr bool absolute_tolerance = AbsoluteTolerance;
static constexpr bool nans_equal = NansEqual;
static constexpr bool signed_zeros_equal = SignedZerosEqual;
+ static constexpr bool ulp_distance_equal = UlpDistanceEqual;
};
template <typename T, typename Flags>
struct FloatingEquality {
explicit FloatingEquality(const EqualOptions& options)
- : epsilon(static_cast<T>(options.atol())) {}
+ : epsilon(static_cast<T>(options.atol().value_or(-1))),
+ ulp_distance(options.ulp_distance().value_or(-1)) {}
bool operator()(T x, T y) const {
if (x == y) {
return Flags::signed_zeros_equal || (std::signbit(x) == std::signbit(y));
}
- if (Flags::nans_equal && std::isnan(x) && std::isnan(y)) {
+
+ if constexpr (Flags::nans_equal) {
+ if (std::isnan(x) && std::isnan(y)) {
+ return true;
+ }
+ } else if (std::isnan(x) || std::isnan(y)) {
+ return false;
+ }
+
+ if (Flags::absolute_tolerance && (std::fabs(x - y) <= epsilon)) {
return true;
}
- if (Flags::approximate && (fabs(x - y) <= epsilon)) {
+ if (Flags::ulp_distance_equal && internal::WithinUlp(x, y, ulp_distance)) {
return true;
}
return false;
}
const T epsilon;
+ const int32_t ulp_distance;
};
// For half-float equality.
template <typename Flags>
struct FloatingEquality<uint16_t, Flags> {
explicit FloatingEquality(const EqualOptions& options)
- : epsilon(static_cast<float>(options.atol())) {}
+ : epsilon(static_cast<float>(options.atol().value_or(-1))),
+ ulp_distance(options.ulp_distance().value_or(-1)) {}
bool operator()(uint16_t x, uint16_t y) const {
Float16 f_x = Float16::FromBits(x);
@@ -111,46 +126,65 @@ struct FloatingEquality<uint16_t, Flags> {
if (f_x == f_y) {
return Flags::signed_zeros_equal || (f_x.signbit() == f_y.signbit());
}
- if (Flags::nans_equal && f_x.is_nan() && f_y.is_nan()) {
+ if constexpr (Flags::nans_equal) {
+ if (f_x.is_nan() && f_y.is_nan()) {
+ return true;
+ }
+ } else if (f_x.is_nan() || f_y.is_nan()) {
+ return false;
+ }
+ if (Flags::absolute_tolerance &&
+ (std::fabs(f_x.ToFloat() - f_y.ToFloat()) <= epsilon)) {
return true;
}
- if (Flags::approximate && (fabs(f_x.ToFloat() - f_y.ToFloat()) <=
epsilon)) {
+ if (Flags::ulp_distance_equal && internal::WithinUlp(f_x, f_y,
ulp_distance)) {
return true;
}
return false;
}
const float epsilon;
+ const int32_t ulp_distance;
};
template <typename T, typename Visitor>
struct FloatingEqualityDispatcher {
const EqualOptions& options;
- bool floating_approximate;
Visitor&& visit;
- template <bool Approximate, bool NansEqual>
- void DispatchL3() {
- if (options.signed_zeros_equal()) {
- visit(FloatingEquality<T, FloatingEqualityFlags<Approximate, NansEqual,
true>>{
+ template <bool AbsoluteTolerance, bool NansEqual, bool SignedZero>
+ void DispatchL4() {
+ if (options.ulp_distance()) {
+ visit(FloatingEquality<
+ T, FloatingEqualityFlags<AbsoluteTolerance, NansEqual, SignedZero,
true>>{
options});
} else {
- visit(FloatingEquality<T, FloatingEqualityFlags<Approximate, NansEqual,
false>>{
+ visit(FloatingEquality<
+ T, FloatingEqualityFlags<AbsoluteTolerance, NansEqual, SignedZero,
false>>{
options});
}
}
- template <bool Approximate>
+ template <bool AbsoluteTolerance, bool NansEqual>
+ void DispatchL3() {
+ if (options.signed_zeros_equal()) {
+ DispatchL4<AbsoluteTolerance, NansEqual, true>();
+ } else {
+ DispatchL4<AbsoluteTolerance, NansEqual, false>();
+ }
+ }
+
+ template <bool AbsoluteTolerance>
void DispatchL2() {
if (options.nans_equal()) {
- DispatchL3<Approximate, true>();
+ DispatchL3<AbsoluteTolerance, true>();
} else {
- DispatchL3<Approximate, false>();
+ DispatchL3<AbsoluteTolerance, false>();
}
}
void Dispatch() {
- if (floating_approximate) {
+ if (options.atol()) {
DispatchL2<true>();
} else {
DispatchL2<false>();
@@ -161,10 +195,8 @@ struct FloatingEqualityDispatcher {
// Call `visit(equality_func)` where `equality_func` has the signature
`bool(T, T)`
// and returns true if the two values compare equal.
template <typename T, typename Visitor>
-void VisitFloatingEquality(const EqualOptions& options, bool
floating_approximate,
- Visitor&& visit) {
- FloatingEqualityDispatcher<T, Visitor>{options, floating_approximate,
- std::forward<Visitor>(visit)}
+void VisitFloatingEquality(const EqualOptions& options, Visitor&& visit) {
+ FloatingEqualityDispatcher<T, Visitor>{options, std::forward<Visitor>(visit)}
.Dispatch();
}
@@ -190,20 +222,17 @@ inline bool IdentityImpliesEquality(const DataType& type,
const EqualOptions& op
bool CompareArrayRanges(const ArrayData& left, const ArrayData& right,
int64_t left_start_idx, int64_t left_end_idx,
- int64_t right_start_idx, const EqualOptions& options,
- bool floating_approximate);
+ int64_t right_start_idx, const EqualOptions& options);
class RangeDataEqualsImpl {
public:
// PRE-CONDITIONS:
// - the types are equal
// - the ranges are in bounds
- RangeDataEqualsImpl(const EqualOptions& options, bool floating_approximate,
- const ArrayData& left, const ArrayData& right,
- int64_t left_start_idx, int64_t right_start_idx,
- int64_t range_length)
+ RangeDataEqualsImpl(const EqualOptions& options, const ArrayData& left,
+ const ArrayData& right, int64_t left_start_idx,
+ int64_t right_start_idx, int64_t range_length)
: options_(options),
- floating_approximate_(floating_approximate),
left_(left),
right_(right),
left_start_idx_(left_start_idx),
@@ -349,7 +378,7 @@ class RangeDataEqualsImpl {
const ArrayData& right_data = *right_.child_data[0];
auto compare_runs = [&](int64_t i, int64_t length) -> bool {
- RangeDataEqualsImpl impl(options_, floating_approximate_, left_data,
right_data,
+ RangeDataEqualsImpl impl(options_, left_data, right_data,
(left_start_idx_ + left_.offset + i) *
list_size,
(right_start_idx_ + right_.offset + i) *
list_size,
length * list_size);
@@ -364,8 +393,7 @@ class RangeDataEqualsImpl {
auto compare_runs = [&](int64_t i, int64_t length) -> bool {
for (int32_t f = 0; f < num_fields; ++f) {
- RangeDataEqualsImpl impl(options_, floating_approximate_,
*left_.child_data[f],
- *right_.child_data[f],
+ RangeDataEqualsImpl impl(options_, *left_.child_data[f],
*right_.child_data[f],
left_start_idx_ + left_.offset + i,
right_start_idx_ + right_.offset + i, length);
if (!impl.Compare()) {
@@ -399,11 +427,11 @@ class RangeDataEqualsImpl {
const auto previous_child_num = child_ids[left_codes[left_start_idx_ +
i - 1]];
int64_t run_length = i - run_start;
- RangeDataEqualsImpl impl(
- options_, floating_approximate_,
*left_.child_data[previous_child_num],
- *right_.child_data[previous_child_num],
- left_start_idx_ + left_.offset + run_start,
- right_start_idx_ + right_.offset + run_start, run_length);
+ RangeDataEqualsImpl impl(options_,
*left_.child_data[previous_child_num],
+ *right_.child_data[previous_child_num],
+ left_start_idx_ + left_.offset + run_start,
+ right_start_idx_ + right_.offset + run_start,
+ run_length);
if (!impl.Compare()) {
result_ = false;
@@ -421,7 +449,7 @@ class RangeDataEqualsImpl {
int64_t final_run_length = range_length_ - run_start;
RangeDataEqualsImpl impl(
- options_, floating_approximate_, *left_.child_data[final_child_num],
+ options_, *left_.child_data[final_child_num],
*right_.child_data[final_child_num], left_start_idx_ + left_.offset
+ run_start,
right_start_idx_ + right_.offset + run_start, final_run_length);
@@ -447,9 +475,8 @@ class RangeDataEqualsImpl {
}
const auto child_num = child_ids[type_id];
RangeDataEqualsImpl impl(
- options_, floating_approximate_, *left_.child_data[child_num],
- *right_.child_data[child_num], left_offsets[left_start_idx_ + i],
- right_offsets[right_start_idx_ + i], 1);
+ options_, *left_.child_data[child_num],
*right_.child_data[child_num],
+ left_offsets[left_start_idx_ + i], right_offsets[right_start_idx_ +
i], 1);
if (!impl.Compare()) {
result_ = false;
break;
@@ -464,7 +491,7 @@ class RangeDataEqualsImpl {
*left_.dictionary, *right_.dictionary,
/*left_start_idx=*/0,
/*left_end_idx=*/std::max(left_.dictionary->length,
right_.dictionary->length),
- /*right_start_idx=*/0, options_, floating_approximate_);
+ /*right_start_idx=*/0, options_);
if (result_) {
// Compare indices
result_ &= CompareWithType(*type.index_type());
@@ -516,7 +543,7 @@ class RangeDataEqualsImpl {
return compare_func(x, y);
});
};
- VisitFloatingEquality<CType>(options_, floating_approximate_,
std::move(visitor));
+ VisitFloatingEquality<CType>(options_, std::move(visitor));
return Status::OK();
}
@@ -547,8 +574,8 @@ class RangeDataEqualsImpl {
const auto compare_ranges = [&](int64_t left_offset, int64_t right_offset,
int64_t length) -> bool {
- RangeDataEqualsImpl impl(options_, floating_approximate_, left_data,
right_data,
- left_offset, right_offset, length);
+ RangeDataEqualsImpl impl(options_, left_data, right_data, left_offset,
right_offset,
+ length);
return impl.Compare();
};
@@ -576,8 +603,8 @@ class RangeDataEqualsImpl {
if (size == 0) {
continue;
}
- RangeDataEqualsImpl impl(options_, floating_approximate_, left_values,
- right_values, left_offsets[j],
right_offsets[j], size);
+ RangeDataEqualsImpl impl(options_, left_values, right_values,
left_offsets[j],
+ right_offsets[j], size);
if (!impl.Compare()) {
return false;
}
@@ -602,7 +629,7 @@ class RangeDataEqualsImpl {
auto it = ree_util::MergedRunsIterator(left, right);
for (; !it.is_end(); ++it) {
- RangeDataEqualsImpl impl(options_, floating_approximate_, left_values,
right_values,
+ RangeDataEqualsImpl impl(options_, left_values, right_values,
it.index_into_left_array(),
it.index_into_right_array(),
/*range_length=*/1);
if (!impl.Compare()) {
@@ -670,7 +697,6 @@ class RangeDataEqualsImpl {
}
const EqualOptions& options_;
- const bool floating_approximate_;
const ArrayData& left_;
const ArrayData& right_;
const int64_t left_start_idx_;
@@ -682,8 +708,7 @@ class RangeDataEqualsImpl {
bool CompareArrayRanges(const ArrayData& left, const ArrayData& right,
int64_t left_start_idx, int64_t left_end_idx,
- int64_t right_start_idx, const EqualOptions& options,
- bool floating_approximate) {
+ int64_t right_start_idx, const EqualOptions& options) {
if (left.type->id() != right.type->id() ||
!TypeEquals(*left.type, *right.type, false /* check_metadata */)) {
return false;
@@ -704,8 +729,8 @@ bool CompareArrayRanges(const ArrayData& left, const
ArrayData& right,
return true;
}
// Compare values
- RangeDataEqualsImpl impl(options, floating_approximate, left, right,
left_start_idx,
- right_start_idx, range_length);
+ RangeDataEqualsImpl impl(options, left, right, left_start_idx,
right_start_idx,
+ range_length);
return impl.Compare();
}
@@ -875,22 +900,13 @@ class TypeEqualsVisitor {
bool result_;
};
-bool ArrayEquals(const Array& left, const Array& right, const EqualOptions&
opts,
- bool floating_approximate);
-bool ScalarEquals(const Scalar& left, const Scalar& right, const EqualOptions&
options,
- bool floating_approximate);
-
class ScalarEqualsVisitor {
public:
// PRE-CONDITIONS:
// - the types are equal
// - the scalars are non-null
- explicit ScalarEqualsVisitor(const Scalar& right, const EqualOptions& opts,
- bool floating_approximate)
- : right_(right),
- options_(opts),
- floating_approximate_(floating_approximate),
- result_(false) {}
+ explicit ScalarEqualsVisitor(const Scalar& right, const EqualOptions& opts)
+ : right_(right), options_(opts), result_(false) {}
Status Visit(const NullScalar& left) {
result_ = true;
@@ -952,37 +968,37 @@ class ScalarEqualsVisitor {
Status Visit(const ListScalar& left) {
const auto& right = checked_cast<const ListScalar&>(right_);
- result_ = ArrayEquals(*left.value, *right.value, options_,
floating_approximate_);
+ result_ = ArrayEquals(*left.value, *right.value, options_);
return Status::OK();
}
Status Visit(const LargeListScalar& left) {
const auto& right = checked_cast<const LargeListScalar&>(right_);
- result_ = ArrayEquals(*left.value, *right.value, options_,
floating_approximate_);
+ result_ = ArrayEquals(*left.value, *right.value, options_);
return Status::OK();
}
Status Visit(const ListViewScalar& left) {
const auto& right = checked_cast<const ListViewScalar&>(right_);
- result_ = ArrayEquals(*left.value, *right.value, options_,
floating_approximate_);
+ result_ = ArrayEquals(*left.value, *right.value, options_);
return Status::OK();
}
Status Visit(const LargeListViewScalar& left) {
const auto& right = checked_cast<const LargeListViewScalar&>(right_);
- result_ = ArrayEquals(*left.value, *right.value, options_,
floating_approximate_);
+ result_ = ArrayEquals(*left.value, *right.value, options_);
return Status::OK();
}
Status Visit(const MapScalar& left) {
const auto& right = checked_cast<const MapScalar&>(right_);
- result_ = ArrayEquals(*left.value, *right.value, options_,
floating_approximate_);
+ result_ = ArrayEquals(*left.value, *right.value, options_);
return Status::OK();
}
Status Visit(const FixedSizeListScalar& left) {
const auto& right = checked_cast<const FixedSizeListScalar&>(right_);
- result_ = ArrayEquals(*left.value, *right.value, options_,
floating_approximate_);
+ result_ = ArrayEquals(*left.value, *right.value, options_);
return Status::OK();
}
@@ -994,8 +1010,7 @@ class ScalarEqualsVisitor {
} else {
bool all_equals = true;
for (size_t i = 0; i < left.value.size() && all_equals; i++) {
- all_equals &= ScalarEquals(*left.value[i], *right.value[i], options_,
- floating_approximate_);
+ all_equals &= ScalarEquals(*left.value[i], *right.value[i], options_);
}
result_ = all_equals;
}
@@ -1005,35 +1020,33 @@ class ScalarEqualsVisitor {
Status Visit(const DenseUnionScalar& left) {
const auto& right = checked_cast<const DenseUnionScalar&>(right_);
- result_ = ScalarEquals(*left.value, *right.value, options_,
floating_approximate_);
+ result_ = ScalarEquals(*left.value, *right.value, options_);
return Status::OK();
}
Status Visit(const SparseUnionScalar& left) {
const auto& right = checked_cast<const SparseUnionScalar&>(right_);
- result_ = ScalarEquals(*left.value[left.child_id],
*right.value[right.child_id],
- options_, floating_approximate_);
+ result_ =
+ ScalarEquals(*left.value[left.child_id], *right.value[right.child_id],
options_);
return Status::OK();
}
Status Visit(const DictionaryScalar& left) {
const auto& right = checked_cast<const DictionaryScalar&>(right_);
- result_ = ScalarEquals(*left.value.index, *right.value.index, options_,
- floating_approximate_) &&
- ArrayEquals(*left.value.dictionary, *right.value.dictionary,
options_,
- floating_approximate_);
+ result_ = ScalarEquals(*left.value.index, *right.value.index, options_) &&
+ ArrayEquals(*left.value.dictionary, *right.value.dictionary,
options_);
return Status::OK();
}
Status Visit(const RunEndEncodedScalar& left) {
const auto& right = checked_cast<const RunEndEncodedScalar&>(right_);
- result_ = ScalarEquals(*left.value, *right.value, options_,
floating_approximate_);
+ result_ = ScalarEquals(*left.value, *right.value, options_);
return Status::OK();
}
Status Visit(const ExtensionScalar& left) {
const auto& right = checked_cast<const ExtensionScalar&>(right_);
- result_ = ScalarEquals(*left.value, *right.value, options_,
floating_approximate_);
+ result_ = ScalarEquals(*left.value, *right.value, options_);
return Status::OK();
}
@@ -1048,13 +1061,12 @@ class ScalarEqualsVisitor {
auto visitor = [&](auto&& compare_func) {
result_ = compare_func(left.value, right.value);
};
- VisitFloatingEquality<CType>(options_, floating_approximate_,
std::move(visitor));
+ VisitFloatingEquality<CType>(options_, std::move(visitor));
return Status::OK();
}
const Scalar& right_;
const EqualOptions options_;
- const bool floating_approximate_;
bool result_;
};
@@ -1107,12 +1119,13 @@ Status PrintDiff(const Array& left, const Array& right,
std::ostream* os) {
return PrintDiff(left, right, 0, left.length(), 0, right.length(), os);
}
+} // namespace
+
bool ArrayRangeEquals(const Array& left, const Array& right, int64_t
left_start_idx,
int64_t left_end_idx, int64_t right_start_idx,
- const EqualOptions& options, bool floating_approximate) {
- bool are_equal =
- CompareArrayRanges(*left.data(), *right.data(), left_start_idx,
left_end_idx,
- right_start_idx, options, floating_approximate);
+ const EqualOptions& options) {
+ bool are_equal = CompareArrayRanges(*left.data(), *right.data(),
left_start_idx,
+ left_end_idx, right_start_idx, options);
if (!are_equal) {
ARROW_IGNORE_EXPR(PrintDiff(
left, right, left_start_idx, left_end_idx, right_start_idx,
@@ -1121,17 +1134,34 @@ bool ArrayRangeEquals(const Array& left, const Array&
right, int64_t left_start_
return are_equal;
}
-bool ArrayEquals(const Array& left, const Array& right, const EqualOptions&
opts,
- bool floating_approximate) {
+bool ArrayRangeApproxEquals(const Array& left, const Array& right, int64_t
left_start_idx,
+ int64_t left_end_idx, int64_t right_start_idx,
+ const EqualOptions& options) {
+ auto resolved_options = options;
+ if (!resolved_options.atol()) {
+ resolved_options = options.atol(kDefaultAbsoluteTolerance);
+ }
+ return ArrayRangeEquals(left, right, left_start_idx, left_end_idx,
right_start_idx,
+ resolved_options);
+}
+
+bool ArrayEquals(const Array& left, const Array& right, const EqualOptions&
opts) {
if (left.length() != right.length()) {
ARROW_IGNORE_EXPR(PrintDiff(left, right, opts.diff_sink()));
return false;
}
- return ArrayRangeEquals(left, right, 0, left.length(), 0, opts,
floating_approximate);
+ return ArrayRangeEquals(left, right, 0, left.length(), 0, opts);
+}
+
+bool ArrayApproxEquals(const Array& left, const Array& right, const
EqualOptions& opts) {
+ auto resolved_options = opts;
+ if (!resolved_options.atol()) {
+ resolved_options = resolved_options.atol(kDefaultAbsoluteTolerance);
+ }
+ return ArrayEquals(left, right, resolved_options);
}
-bool ScalarEquals(const Scalar& left, const Scalar& right, const EqualOptions&
options,
- bool floating_approximate) {
+bool ScalarEquals(const Scalar& left, const Scalar& right, const EqualOptions&
options) {
if (&left == &right && IdentityImpliesEquality(*left.type, options)) {
return true;
}
@@ -1144,46 +1174,19 @@ bool ScalarEquals(const Scalar& left, const Scalar&
right, const EqualOptions& o
if (!left.is_valid) {
return true;
}
- ScalarEqualsVisitor visitor(right, options, floating_approximate);
+ ScalarEqualsVisitor visitor(right, options);
auto error = VisitScalarInline(left, &visitor);
DCHECK_OK(error);
return visitor.result();
}
-} // namespace
-
-bool ArrayRangeEquals(const Array& left, const Array& right, int64_t
left_start_idx,
- int64_t left_end_idx, int64_t right_start_idx,
- const EqualOptions& options) {
- return ArrayRangeEquals(left, right, left_start_idx, left_end_idx,
right_start_idx,
- options, options.use_atol());
-}
-
-bool ArrayRangeApproxEquals(const Array& left, const Array& right, int64_t
left_start_idx,
- int64_t left_end_idx, int64_t right_start_idx,
- const EqualOptions& options) {
- const bool floating_approximate = true;
- return ArrayRangeEquals(left, right, left_start_idx, left_end_idx,
right_start_idx,
- options, floating_approximate);
-}
-
-bool ArrayEquals(const Array& left, const Array& right, const EqualOptions&
opts) {
- return ArrayEquals(left, right, opts, opts.use_atol());
-}
-
-bool ArrayApproxEquals(const Array& left, const Array& right, const
EqualOptions& opts) {
- const bool floating_approximate = true;
- return ArrayEquals(left, right, opts, floating_approximate);
-}
-
-bool ScalarEquals(const Scalar& left, const Scalar& right, const EqualOptions&
options) {
- return ScalarEquals(left, right, options, options.use_atol());
-}
-
bool ScalarApproxEquals(const Scalar& left, const Scalar& right,
const EqualOptions& options) {
- const bool floating_approximate = true;
- return ScalarEquals(left, right, options, floating_approximate);
+ auto resolved_options = options;
+ if (!options.atol()) {
+ resolved_options = options.atol(kDefaultAbsoluteTolerance);
+ }
+ return ScalarEquals(left, right, resolved_options);
}
namespace {
@@ -1274,8 +1277,7 @@ bool StridedFloatTensorContentEquals(const int dim_index,
int64_t left_offset,
}
};
- VisitFloatingEquality<c_type>(opts, /*floating_approximate=*/false,
- std::move(visitor));
+ VisitFloatingEquality<c_type>(opts, std::move(visitor));
return result;
}
@@ -1528,7 +1530,7 @@ namespace {
bool DoubleEquals(const double& left, const double& right, const EqualOptions&
options) {
bool result;
auto visitor = [&](auto&& compare_func) { result = compare_func(left,
right); };
- VisitFloatingEquality<double>(options, options.use_atol(),
std::move(visitor));
+ VisitFloatingEquality<double>(options, std::move(visitor));
return result;
}
diff --git a/cpp/src/arrow/compare.h b/cpp/src/arrow/compare.h
index 2198495d7d..27b82c34c4 100644
--- a/cpp/src/arrow/compare.h
+++ b/cpp/src/arrow/compare.h
@@ -21,6 +21,7 @@
#include <cstdint>
#include <iosfwd>
+#include <optional>
#include "arrow/util/macros.h"
#include "arrow/util/visibility.h"
@@ -35,6 +36,7 @@ class SparseTensor;
struct Scalar;
static constexpr double kDefaultAbsoluteTolerance = 1E-5;
+static constexpr int32_t kDefaultUlpDistance = 4;
/// A container of options for equality comparisons
class EqualOptions {
@@ -63,26 +65,49 @@ class EqualOptions {
///
/// This option only affects the Equals methods
/// and has no effect on ApproxEquals methods.
- bool use_atol() const { return use_atol_; }
+ /// \deprecated Deprecated in 25.0.0. Use arrow::EqualOptions::atol instead
+ ARROW_DEPRECATED("Deprecated in 25.0.0. Use arrow::EqualOptions::atol
instead")
+ bool use_atol() const { return atol_.has_value(); }
/// Return a new EqualOptions object with the "use_atol" property changed.
+ /// \deprecated Deprecated in 25.0.0. Use arrow::EqualOptions::atol instead
+ ARROW_DEPRECATED("Deprecated in 25.0.0. Use arrow::EqualOptions::atol
instead")
EqualOptions use_atol(bool v) const {
auto res = EqualOptions(*this);
- res.use_atol_ = v;
+ if (v) {
+ res.atol_ = atol_.value_or(kDefaultAbsoluteTolerance);
+ } else {
+ res.atol_ = std::nullopt;
+ }
return res;
}
/// The absolute tolerance for approximate comparisons of floating-point
values.
- /// Note that this option is ignored if "use_atol" is set to false.
- double atol() const { return atol_; }
+ std::optional<double> atol() const { return atol_; }
/// Return a new EqualOptions object with the "atol" property changed.
- EqualOptions atol(double v) const {
+ /// If both "ulp_distance" and "atol" are specified, the comparison
+ /// succeeds when the "ulp_distance" condition OR the "atol" condition
+ /// is satisfied.
+ EqualOptions atol(std::optional<double> v) const {
auto res = EqualOptions(*this);
res.atol_ = v;
return res;
}
+ /// The ulp distance for approximate comparisons of floating-point values.
+ std::optional<int32_t> ulp_distance() const { return ulp_distance_; }
+
+ /// Return a new EqualOptions object with the "ulp_distance" property
changed.
+ /// If both "ulp_distance" and "atol" are specified, the comparison
+ /// succeeds when the "ulp_distance" condition OR the "atol" condition
+ /// is satisfied.
+ EqualOptions ulp_distance(std::optional<int32_t> v) const {
+ auto res = EqualOptions(*this);
+ res.ulp_distance_ = v;
+ return res;
+ }
+
/// Whether the \ref arrow::Schema property is used in the comparison.
///
/// This option only affects the Equals methods
@@ -131,10 +156,10 @@ class EqualOptions {
static EqualOptions Defaults() { return {}; }
protected:
- double atol_ = kDefaultAbsoluteTolerance;
+ std::optional<double> atol_ = std::nullopt;
+ std::optional<int32_t> ulp_distance_ = std::nullopt;
bool nans_equal_ = false;
bool signed_zeros_equal_ = true;
- bool use_atol_ = false;
bool use_schema_ = true;
bool use_metadata_ = false;
@@ -150,6 +175,9 @@ ARROW_EXPORT bool ArrayEquals(const Array& left, const
Array& right,
/// Returns true if the arrays are approximately equal. For non-floating point
/// types, this is equivalent to ArrayEquals(left, right)
///
+/// If the absolute tolerance (atol) is not specified in \ref
arrow::EqualOptions,
+/// 'arrow::kDefaultAbsoluteTolerance' is used.
+///
/// Note that arrow::ArrayStatistics is not included in the comparison.
ARROW_EXPORT bool ArrayApproxEquals(const Array& left, const Array& right,
const EqualOptions& =
EqualOptions::Defaults());
@@ -164,6 +192,9 @@ ARROW_EXPORT bool ArrayRangeEquals(const Array& left, const
Array& right,
/// Returns true if indicated equal-length segment of arrays are approximately
equal
///
+/// If the absolute tolerance (atol) is not specified in \ref
arrow::EqualOptions,
+/// 'arrow::kDefaultAbsoluteTolerance' is used.
+///
/// Note that arrow::ArrayStatistics is not included in the comparison.
ARROW_EXPORT bool ArrayRangeApproxEquals(const Array& left, const Array& right,
int64_t start_idx, int64_t end_idx,
@@ -203,6 +234,10 @@ ARROW_EXPORT bool ScalarEquals(const Scalar& left, const
Scalar& right,
const EqualOptions& options =
EqualOptions::Defaults());
/// Returns true if scalars are approximately equal
+///
+/// If the absolute tolerance (atol) is not specified in \ref
arrow::EqualOptions,
+/// 'arrow::kDefaultAbsoluteTolerance' is used.
+///
/// \param[in] left a Scalar
/// \param[in] right a Scalar
/// \param[in] options comparison options
diff --git a/cpp/src/arrow/meson.build b/cpp/src/arrow/meson.build
index 4b8faebecf..831bc12180 100644
--- a/cpp/src/arrow/meson.build
+++ b/cpp/src/arrow/meson.build
@@ -214,6 +214,7 @@ arrow_util_srcs = [
'util/time.cc',
'util/tracing.cc',
'util/trie.cc',
+ 'util/ulp_distance.cc',
'util/union_util.cc',
'util/unreachable.cc',
'util/uri.cc',
diff --git a/cpp/src/arrow/record_batch.h b/cpp/src/arrow/record_batch.h
index 0d1d2d4ac3..17d7f9857a 100644
--- a/cpp/src/arrow/record_batch.h
+++ b/cpp/src/arrow/record_batch.h
@@ -137,12 +137,19 @@ class ARROW_EXPORT RecordBatch {
/// \brief Determine if two record batches are approximately equal
///
+ /// If the absolute tolerance (atol) is not specified in \ref
arrow::EqualOptions,
+ /// 'arrow::kDefaultAbsoluteTolerance' is used.
+ ///
/// \param[in] other the RecordBatch to compare with
/// \param[in] opts the options for equality comparisons
/// \return true if batches are approximately equal
bool ApproxEquals(const RecordBatch& other,
const EqualOptions& opts = EqualOptions::Defaults()) const
{
- return Equals(other, opts.use_schema(false).use_atol(true));
+ auto resolved_options = opts.use_schema(false);
+ if (!resolved_options.atol()) {
+ resolved_options = resolved_options.atol(kDefaultAbsoluteTolerance);
+ }
+ return Equals(other, resolved_options);
}
/// \return the record batch's schema
diff --git a/cpp/src/arrow/record_batch_test.cc
b/cpp/src/arrow/record_batch_test.cc
index a037d7261e..904285fd1c 100644
--- a/cpp/src/arrow/record_batch_test.cc
+++ b/cpp/src/arrow/record_batch_test.cc
@@ -208,8 +208,12 @@ TEST_F(TestRecordBatchEqualOptions, Approx) {
EXPECT_FALSE(b1->ApproxEquals(*b2,
EqualOptions::Defaults().nans_equal(true)));
auto options = EqualOptions::Defaults().nans_equal(true).atol(0.1);
- EXPECT_FALSE(b1->Equals(*b2, options));
+ EXPECT_FALSE(b1->Equals(*b2));
+ EXPECT_TRUE(b1->Equals(*b2, options));
+ ARROW_SUPPRESS_DEPRECATION_WARNING
EXPECT_TRUE(b1->Equals(*b2, options.use_atol(true)));
+ EXPECT_FALSE(b1->Equals(*b2, options.use_atol(false)));
+ ARROW_UNSUPPRESS_DEPRECATION_WARNING
EXPECT_TRUE(b1->ApproxEquals(*b2, options));
}
diff --git a/cpp/src/arrow/scalar_test.cc b/cpp/src/arrow/scalar_test.cc
index 4a34e5d13c..4096b8cd17 100644
--- a/cpp/src/arrow/scalar_test.cc
+++ b/cpp/src/arrow/scalar_test.cc
@@ -20,11 +20,13 @@
#include <memory>
#include <ostream>
#include <string>
+#include <type_traits>
#include <unordered_set>
#include <utility>
#include <vector>
#include <gmock/gmock.h>
+#include <gtest/gtest-spi.h>
#include <gtest/gtest.h>
#include "arrow/array.h"
@@ -408,8 +410,12 @@ class TestRealScalar : public ::testing::Test {
void TestUseAtol() {
auto options = EqualOptions::Defaults().atol(0.2f);
- ASSERT_FALSE(scalar_val_->Equals(*scalar_other_, options));
+ ASSERT_FALSE(scalar_val_->Equals(*scalar_other_));
+ ASSERT_TRUE(scalar_val_->Equals(*scalar_other_, options));
+ ARROW_SUPPRESS_DEPRECATION_WARNING
ASSERT_TRUE(scalar_val_->Equals(*scalar_other_, options.use_atol(true)));
+ ASSERT_FALSE(scalar_val_->Equals(*scalar_other_, options.use_atol(false)));
+ ARROW_UNSUPPRESS_DEPRECATION_WARNING
ASSERT_TRUE(scalar_val_->ApproxEquals(*scalar_other_, options));
}
@@ -434,8 +440,8 @@ class TestRealScalar : public ::testing::Test {
ASSERT_FALSE(struct_nan.ApproxEquals(struct_other_nan, options));
options = options.atol(0.15);
- ASSERT_FALSE(struct_val.Equals(struct_other_val, options));
- ASSERT_FALSE(struct_other_val.Equals(struct_val, options));
+ ASSERT_TRUE(struct_val.Equals(struct_other_val, options));
+ ASSERT_TRUE(struct_other_val.Equals(struct_val, options));
ASSERT_FALSE(struct_nan.Equals(struct_val, options));
ASSERT_FALSE(struct_nan.Equals(struct_nan, options));
ASSERT_FALSE(struct_nan.Equals(struct_other_nan, options));
@@ -446,8 +452,8 @@ class TestRealScalar : public ::testing::Test {
ASSERT_FALSE(struct_nan.ApproxEquals(struct_other_nan, options));
options = options.nans_equal(true);
- ASSERT_FALSE(struct_val.Equals(struct_other_val, options));
- ASSERT_FALSE(struct_other_val.Equals(struct_val, options));
+ ASSERT_TRUE(struct_val.Equals(struct_other_val, options));
+ ASSERT_TRUE(struct_other_val.Equals(struct_val, options));
ASSERT_FALSE(struct_nan.Equals(struct_val, options));
ASSERT_TRUE(struct_nan.Equals(struct_nan, options));
ASSERT_TRUE(struct_nan.Equals(struct_other_nan, options));
@@ -491,7 +497,7 @@ class TestRealScalar : public ::testing::Test {
options = options.atol(0.15);
ASSERT_TRUE(list_val.Equals(list_val, options));
- ASSERT_FALSE(list_val.Equals(list_other_val, options));
+ ASSERT_TRUE(list_val.Equals(list_other_val, options));
ASSERT_FALSE(list_nan.Equals(list_val, options));
ASSERT_FALSE(list_nan.Equals(list_nan, options));
ASSERT_FALSE(list_nan.Equals(list_other_nan, options));
@@ -503,7 +509,7 @@ class TestRealScalar : public ::testing::Test {
options = options.nans_equal(true);
ASSERT_TRUE(list_val.Equals(list_val, options));
- ASSERT_FALSE(list_val.Equals(list_other_val, options));
+ ASSERT_TRUE(list_val.Equals(list_other_val, options));
ASSERT_FALSE(list_nan.Equals(list_val, options));
ASSERT_TRUE(list_nan.Equals(list_nan, options));
ASSERT_TRUE(list_nan.Equals(list_other_nan, options));
@@ -562,6 +568,75 @@ TYPED_TEST(TestRealScalar, ListViewOf) {
this->TestListViewOf(); }
TYPED_TEST(TestRealScalar, LargeListViewOf) { this->TestLargeListViewOf(); }
+namespace {
+
+template <typename CType>
+void AssertScalarsEqual(const CType& left, const CType& right,
+ const EqualOptions& options) {
+ using ScalarType = TypeTraits<typename
CTypeTraits<CType>::ArrowType>::ScalarType;
+ arrow::AssertScalarsEqual(ScalarType(left), ScalarType(right), false,
options);
+}
+
+} // namespace
+
+TEST(TestRealScalarUlpDistance, Double) {
+ // 'static' ensures the variable outlives EXPECT_FATAL_FAILURE's separate
execution
+ // context.
+ static auto options = EqualOptions::Defaults();
+ AssertScalarsEqual(0.9999999999999988, 1.0000000000000007,
options.ulp_distance(14));
+#ifndef _WIN32
+ // GH-47442
+ EXPECT_FATAL_FAILURE(AssertScalarsEqual(0.9999999999999988,
1.0000000000000007,
+ options.ulp_distance(13)),
+ "");
+ EXPECT_FATAL_FAILURE(
+ AssertScalarsEqual(0.9999999999999988,
std::numeric_limits<double>::quiet_NaN(),
+ options.ulp_distance(14).nans_equal(true)),
+ "");
+ EXPECT_FATAL_FAILURE(
+ AssertScalarsEqual(0.9999999999999988,
std::numeric_limits<double>::quiet_NaN(),
+ options.ulp_distance(14).nans_equal(false)),
+ "");
+#endif
+}
+
+TEST(TestRealScalarUlpDistance, Float) {
+ static auto options = EqualOptions::Defaults();
+ AssertScalarsEqual(123.456f, 123.456085f, options.ulp_distance(11));
+#ifndef _WIN32
+ // GH-47442
+ EXPECT_FATAL_FAILURE(
+ AssertScalarsEqual(123.456f, 123.456085f, options.ulp_distance(10)), "");
+ EXPECT_FATAL_FAILURE(
+ AssertScalarsEqual(123.456f, std::numeric_limits<float>::quiet_NaN(),
+ options.ulp_distance(11).nans_equal(true)),
+ "");
+ EXPECT_FATAL_FAILURE(
+ AssertScalarsEqual(123.456f, std::numeric_limits<float>::quiet_NaN(),
+ options.ulp_distance(11).nans_equal(false)),
+ "");
+#endif
+}
+
+TEST(TestRealScalarUlpDistance, HalfFloat) {
+ static auto options = EqualOptions::Defaults();
+ AssertScalarsEqual(Float16(1.00097656), Float16(0.999511719f),
options.ulp_distance(2));
+#ifndef _WIN32
+ // GH-47442
+ EXPECT_FATAL_FAILURE(AssertScalarsEqual(Float16(1.00097656),
Float16(0.999511719f),
+ options.ulp_distance(1)),
+ "");
+ EXPECT_FATAL_FAILURE(
+ AssertScalarsEqual(Float16(1.00097656),
std::numeric_limits<Float16>::quiet_NaN(),
+ options.ulp_distance(2).nans_equal(true)),
+ "");
+ EXPECT_FATAL_FAILURE(
+ AssertScalarsEqual(Float16(1.00097656),
std::numeric_limits<Float16>::quiet_NaN(),
+ options.ulp_distance(2).nans_equal(false)),
+ "");
+#endif
+}
+
template <typename T>
class TestDecimalScalar : public ::testing::Test {
public:
diff --git a/cpp/src/arrow/table_test.cc b/cpp/src/arrow/table_test.cc
index 692671910b..4182bf020a 100644
--- a/cpp/src/arrow/table_test.cc
+++ b/cpp/src/arrow/table_test.cc
@@ -306,9 +306,17 @@ TEST(TestTableEqualityFloatType, Approximate) {
ASSERT_FALSE(table->Equals(*other_table, options));
- ASSERT_TRUE(table->Equals(*other_table, options.use_atol(true).atol(1e-3)));
-
- ASSERT_FALSE(table->Equals(*other_table, options.use_atol(true).atol(1e-5)));
+ ASSERT_TRUE(table->Equals(*other_table, options.atol(1e-3)));
+ ARROW_SUPPRESS_DEPRECATION_WARNING
+ ASSERT_TRUE(table->Equals(*other_table, options.atol(1e-3).use_atol(true)));
+ ASSERT_FALSE(table->Equals(*other_table,
options.atol(1e-3).use_atol(false)));
+ ARROW_UNSUPPRESS_DEPRECATION_WARNING
+
+ ASSERT_FALSE(table->Equals(*other_table, options.atol(1e-5)));
+ ARROW_SUPPRESS_DEPRECATION_WARNING
+ ASSERT_FALSE(table->Equals(*other_table, options.use_atol(true)));
+ ASSERT_FALSE(table->Equals(*other_table, options.use_atol(false)));
+ ARROW_UNSUPPRESS_DEPRECATION_WARNING
}
TEST(TestTableEqualitySameAddress, NonFloatType) {
diff --git a/cpp/src/arrow/testing/gtest_util_test.cc
b/cpp/src/arrow/testing/gtest_util_test.cc
index 2a28df79dd..31b5b9e662 100644
--- a/cpp/src/arrow/testing/gtest_util_test.cc
+++ b/cpp/src/arrow/testing/gtest_util_test.cc
@@ -17,7 +17,6 @@
#include <cmath>
#include <memory>
-#include <type_traits>
#include <vector>
#include <gtest/gtest-spi.h>
@@ -180,187 +179,16 @@ TEST_F(TestTensorFromJSON, FromJSON) {
EXPECT_TRUE(tensor_expected->Equals(*result));
}
-template <typename Float>
-void CheckWithinUlpSingle(Float x, Float y, int n_ulp) {
- ARROW_SCOPED_TRACE("x = ", x, ", y = ", y, ", n_ulp = ", n_ulp);
- ASSERT_TRUE(WithinUlp(x, y, n_ulp));
-}
-
-template <typename Float>
-void CheckNotWithinUlpSingle(Float x, Float y, int n_ulp) {
- ARROW_SCOPED_TRACE("x = ", x, ", y = ", y, ", n_ulp = ", n_ulp);
- ASSERT_FALSE(WithinUlp(x, y, n_ulp));
-}
-
-template <typename Float>
-void CheckWithinUlp(Float x, Float y, int n_ulp) {
- CheckWithinUlpSingle(x, y, n_ulp);
- CheckWithinUlpSingle(y, x, n_ulp);
- CheckWithinUlpSingle(x, y, n_ulp + 1);
- CheckWithinUlpSingle(y, x, n_ulp + 1);
- CheckWithinUlpSingle(-x, -y, n_ulp);
- CheckWithinUlpSingle(-y, -x, n_ulp);
-
- for (int exp : {1, -1, 10, -10}) {
- Float x_scaled(0);
- Float y_scaled(0);
- if constexpr (std::is_same_v<Float, Float16>) {
- x_scaled = Float16(std::ldexp(x.ToFloat(), exp));
- y_scaled = Float16(std::ldexp(y.ToFloat(), exp));
- } else {
- x_scaled = std::ldexp(x, exp);
- y_scaled = std::ldexp(y, exp);
- }
- CheckWithinUlpSingle(x_scaled, y_scaled, n_ulp);
- CheckWithinUlpSingle(y_scaled, x_scaled, n_ulp);
- }
-}
-
-template <typename Float>
-void CheckNotWithinUlp(Float x, Float y, int n_ulp) {
- CheckNotWithinUlpSingle(x, y, n_ulp);
- CheckNotWithinUlpSingle(y, x, n_ulp);
- CheckNotWithinUlpSingle(-x, -y, n_ulp);
- CheckNotWithinUlpSingle(-y, -x, n_ulp);
- if (n_ulp > 1) {
- CheckNotWithinUlpSingle(x, y, n_ulp - 1);
- CheckNotWithinUlpSingle(y, x, n_ulp - 1);
- CheckNotWithinUlpSingle(-x, -y, n_ulp - 1);
- CheckNotWithinUlpSingle(-y, -x, n_ulp - 1);
- }
-
- for (int exp : {1, -1, 10, -10}) {
- Float x_scaled(0);
- Float y_scaled(0);
- if constexpr (std::is_same_v<Float, Float16>) {
- x_scaled = Float16(std::ldexp(x.ToFloat(), exp));
- y_scaled = Float16(std::ldexp(y.ToFloat(), exp));
- } else {
- x_scaled = std::ldexp(x, exp);
- y_scaled = std::ldexp(y, exp);
- }
- CheckNotWithinUlpSingle(x_scaled, y_scaled, n_ulp);
- CheckNotWithinUlpSingle(y_scaled, x_scaled, n_ulp);
- }
-}
-
-TEST(TestWithinUlp, Double) {
- for (double f : {0.0, 1e-20, 1.0, 2345678.9}) {
- CheckWithinUlp(f, f, 0);
- CheckWithinUlp(f, f, 1);
- CheckWithinUlp(f, f, 42);
- }
- CheckWithinUlp(-0.0, 0.0, 1);
- CheckWithinUlp(1.0, 1.0000000000000002, 1);
- CheckWithinUlp(1.0, 1.0000000000000007, 3);
- CheckNotWithinUlp(1.0, 1.0000000000000002, 0);
- CheckNotWithinUlp(1.0, 1.0000000000000007, 2);
- CheckNotWithinUlp(1.0, 1.0000000000000007, 1);
- // left and right have a different exponent but are still very close
- CheckWithinUlp(1.0, 0.9999999999999999, 1);
- CheckWithinUlp(1.0, 0.9999999999999988, 11);
- CheckNotWithinUlp(1.0, 0.9999999999999988, 10);
- CheckWithinUlp(1.0000000000000002, 0.9999999999999999, 2);
- CheckNotWithinUlp(1.0000000000000002, 0.9999999999999999, 1);
- CheckWithinUlp(0.9999999999999988, 1.0000000000000007, 14);
- CheckNotWithinUlp(0.9999999999999988, 1.0000000000000007, 13);
-
- CheckWithinUlp(123.4567, 123.45670000000015, 11);
- CheckNotWithinUlp(123.4567, 123.45670000000015, 10);
-
- CheckWithinUlp(HUGE_VAL, HUGE_VAL, 10);
- CheckWithinUlp(-HUGE_VAL, -HUGE_VAL, 10);
- CheckWithinUlp(std::nan(""), std::nan(""), 10);
- CheckNotWithinUlp(HUGE_VAL, -HUGE_VAL, 10);
- CheckNotWithinUlp(12.34, -HUGE_VAL, 10);
- CheckNotWithinUlp(12.34, std::nan(""), 10);
- CheckNotWithinUlp(12.34, -12.34, 10);
- CheckNotWithinUlp(0.0, 1e-20, 10);
-}
-
-TEST(TestWithinUlp, Float) {
- for (float f : {0.0f, 1e-8f, 1.0f, 123.456f}) {
- CheckWithinUlp(f, f, 0);
- CheckWithinUlp(f, f, 1);
- CheckWithinUlp(f, f, 42);
- }
- CheckWithinUlp(-0.0f, 0.0f, 1);
- CheckWithinUlp(1.0f, 1.0000001f, 1);
- CheckWithinUlp(1.0f, 1.0000013f, 11);
- CheckNotWithinUlp(1.0f, 1.0000001f, 0);
- CheckNotWithinUlp(1.0f, 1.0000013f, 10);
- // left and right have a different exponent but are still very close
- CheckWithinUlp(1.0f, 0.99999994f, 1);
- CheckWithinUlp(1.0f, 0.99999934f, 11);
- CheckNotWithinUlp(1.0f, 0.99999934f, 10);
- CheckWithinUlp(1.0000001f, 0.99999994f, 2);
- CheckNotWithinUlp(1.0000001f, 0.99999994f, 1);
- CheckWithinUlp(1.0000013f, 0.99999934f, 22);
- CheckNotWithinUlp(1.0000013f, 0.99999934f, 21);
-
- CheckWithinUlp(123.456f, 123.456085f, 11);
- CheckNotWithinUlp(123.456f, 123.456085f, 10);
-
- CheckWithinUlp(HUGE_VALF, HUGE_VALF, 10);
- CheckWithinUlp(-HUGE_VALF, -HUGE_VALF, 10);
- CheckWithinUlp(std::nanf(""), std::nanf(""), 10);
- CheckNotWithinUlp(HUGE_VALF, -HUGE_VALF, 10);
- CheckNotWithinUlp(12.34f, -HUGE_VALF, 10);
- CheckNotWithinUlp(12.34f, std::nanf(""), 10);
- CheckNotWithinUlp(12.34f, -12.34f, 10);
-}
-
-std::vector<Float16> ConvertToFloat16Vector(const std::vector<float>&
float_values) {
- std::vector<Float16> float16_vector;
- float16_vector.reserve(float_values.size());
- for (auto& value : float_values) {
- float16_vector.emplace_back(value);
- }
- return float16_vector;
-}
-
-TEST(TestWithinUlp, Float16) {
- for (Float16 f : ConvertToFloat16Vector({0.0f, 1e-8f, 1.0f, 123.456f})) {
- CheckWithinUlp(f, f, 0);
- CheckWithinUlp(f, f, 1);
- CheckWithinUlp(f, f, 42);
- }
- CheckWithinUlp(Float16(-0.0f), Float16(0.0f), 1);
- CheckWithinUlp(Float16(1.0f), Float16(1.00097656f), 1);
- CheckWithinUlp(Float16(1.0f), Float16(1.01074219f), 11);
- CheckNotWithinUlp(Float16(1.0f), Float16(1.00097656f), 0);
- CheckNotWithinUlp(Float16(1.0f), Float16(1.01074219f), 10);
- // left and right have a different exponent but are still very close
- CheckWithinUlp(Float16(1.0f), Float16(0.999511719f), 1);
- CheckWithinUlp(Float16(1.0f), Float16(0.994628906f), 11);
- CheckNotWithinUlp(Float16(1.0f), Float16(0.994628906f), 10);
- CheckWithinUlp(Float16(1.00097656), Float16(0.999511719f), 2);
- CheckNotWithinUlp(Float16(1.00097656), Float16(0.999511719f), 1);
- CheckWithinUlp(Float16(1.01074219f), Float16(0.994628906f), 22);
- CheckNotWithinUlp(Float16(1.01074219f), Float16(0.994628906f), 21);
-
- CheckWithinUlp(Float16(123.456f), Float16(124.143501f), 11);
- // The assertion below does not work because ldexp(Float16(124.143501f), 10)
- // results in inf in Float16.
- // CheckNotWithinUlp(Float16(123.456f), Float16(124.143501f), 10);
-
- CheckWithinUlp(std::numeric_limits<Float16>::infinity(),
- std::numeric_limits<Float16>::infinity(), 10);
- CheckWithinUlp(-std::numeric_limits<Float16>::infinity(),
- -std::numeric_limits<Float16>::infinity(), 10);
- CheckWithinUlp(std::numeric_limits<Float16>::quiet_NaN(),
- std::numeric_limits<Float16>::quiet_NaN(), 10);
- CheckNotWithinUlp(std::numeric_limits<Float16>::infinity(),
- -std::numeric_limits<Float16>::infinity(), 10);
- CheckNotWithinUlp(Float16(12.34f),
-std::numeric_limits<Float16>::infinity(), 10);
- CheckNotWithinUlp(Float16(12.34f),
std::numeric_limits<Float16>::quiet_NaN(), 10);
- CheckNotWithinUlp(Float16(12.34f), Float16(-12.34f), 10);
-}
-
TEST(AssertTestWithinUlp, Basics) {
AssertWithinUlp(123.4567, 123.45670000000015, 11);
AssertWithinUlp(123.456f, 123.456085f, 11);
AssertWithinUlp(Float16(123.456f), Float16(124.143501f), 11);
+ AssertWithinUlp(std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(), 2);
+ AssertWithinUlp(std::numeric_limits<double>::quiet_NaN(),
+ std::numeric_limits<double>::quiet_NaN(), 2);
+ AssertWithinUlp(std::numeric_limits<Float16>::quiet_NaN(),
+ std::numeric_limits<Float16>::quiet_NaN(), 2);
#ifndef _WIN32
// GH-47442
EXPECT_FATAL_FAILURE(AssertWithinUlp(123.4567, 123.45670000000015, 10),
diff --git a/cpp/src/arrow/testing/math.cc b/cpp/src/arrow/testing/math.cc
index 79f7ec3033..df65544f06 100644
--- a/cpp/src/arrow/testing/math.cc
+++ b/cpp/src/arrow/testing/math.cc
@@ -25,90 +25,32 @@
#include <gtest/gtest.h>
#include "arrow/util/float16.h"
-#include "arrow/util/logging_internal.h"
-#include "arrow/util/ubsan.h"
+#include "arrow/util/ulp_distance_internal.h"
namespace arrow {
namespace {
template <typename Float>
-struct FloatToUInt;
-
-template <>
-struct FloatToUInt<double> {
- using Type = uint64_t;
-};
-
-template <>
-struct FloatToUInt<float> {
- using Type = uint32_t;
-};
-
-template <>
-struct FloatToUInt<util::Float16> {
- using Type = uint16_t;
-};
-
-template <typename Float>
-struct UlpDistanceUtil {
- public:
- using UIntType = typename FloatToUInt<Float>::Type;
- static constexpr UIntType kNumberOfBits = sizeof(Float) * 8;
- static constexpr UIntType kSignMask = static_cast<UIntType>(1) <<
(kNumberOfBits - 1);
-
- // This implementation is inspired by:
- //
https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
- static UIntType UlpDistance(Float left, Float right) {
- auto unsigned_left = util::SafeCopy<UIntType>(left);
- auto unsigned_right = util::SafeCopy<UIntType>(right);
- auto biased_left = ConvertSignAndMagnitudeToBiased(unsigned_left);
- auto biased_right = ConvertSignAndMagnitudeToBiased(unsigned_right);
- if (biased_left > biased_right) {
- std::swap(biased_left, biased_right);
- }
- return biased_right - biased_left;
- }
-
- private:
- // Source reference (GoogleTest):
- //
https://github.com/google/googletest/blob/1b96fa13f549387b7549cc89e1a785cf143a1a50/googletest/include/gtest/internal/gtest-internal.h#L345-L368
- static UIntType ConvertSignAndMagnitudeToBiased(UIntType value) {
- if (value & kSignMask) {
- return ~value + 1;
- } else {
- return value | kSignMask;
- }
- }
-};
-
-template <typename Float>
-bool WithinUlpGeneric(Float left, Float right, int n_ulps) {
+bool WithinUlpGeneric(Float left, Float right, int32_t n_ulps) {
if constexpr (std::is_same_v<Float, util::Float16>) {
if (left.is_nan() || right.is_nan()) {
return left.is_nan() == right.is_nan();
- } else if (left.is_infinity() || right.is_infinity()) {
- return left == right;
}
} else {
if (std::isnan(left) || std::isnan(right)) {
return std::isnan(left) == std::isnan(right);
}
- if (!std::isfinite(left) || !std::isfinite(right)) {
- return left == right;
- }
}
if (n_ulps == 0) {
return left == right;
}
- DCHECK_GE(n_ulps, 1);
- return UlpDistanceUtil<Float>::UlpDistance(left, right) <=
- static_cast<uint64_t>(n_ulps);
+ return internal::WithinUlp(left, right, n_ulps);
}
template <typename Float>
-void AssertWithinUlpGeneric(Float left, Float right, int n_ulps) {
+void AssertWithinUlpGeneric(Float left, Float right, int32_t n_ulps) {
if (!WithinUlpGeneric(left, right, n_ulps)) {
FAIL() << left << " and " << right << " are not within " << n_ulps << "
ulps";
}
@@ -116,27 +58,15 @@ void AssertWithinUlpGeneric(Float left, Float right, int
n_ulps) {
} // namespace
-bool WithinUlp(util::Float16 left, util::Float16 right, int n_ulps) {
- return WithinUlpGeneric(left, right, n_ulps);
-}
-
-bool WithinUlp(float left, float right, int n_ulps) {
- return WithinUlpGeneric(left, right, n_ulps);
-}
-
-bool WithinUlp(double left, double right, int n_ulps) {
- return WithinUlpGeneric(left, right, n_ulps);
-}
-
-void AssertWithinUlp(util::Float16 left, util::Float16 right, int n_ulps) {
+void AssertWithinUlp(util::Float16 left, util::Float16 right, int32_t n_ulps) {
AssertWithinUlpGeneric(left, right, n_ulps);
}
-void AssertWithinUlp(float left, float right, int n_ulps) {
+void AssertWithinUlp(float left, float right, int32_t n_ulps) {
AssertWithinUlpGeneric(left, right, n_ulps);
}
-void AssertWithinUlp(double left, double right, int n_ulps) {
+void AssertWithinUlp(double left, double right, int32_t n_ulps) {
AssertWithinUlpGeneric(left, right, n_ulps);
}
diff --git a/cpp/src/arrow/testing/math.h b/cpp/src/arrow/testing/math.h
index 1e829e0d61..ffe9628a02 100644
--- a/cpp/src/arrow/testing/math.h
+++ b/cpp/src/arrow/testing/math.h
@@ -23,17 +23,10 @@
namespace arrow {
ARROW_TESTING_EXPORT
-bool WithinUlp(util::Float16 left, util::Float16 right, int n_ulps);
+void AssertWithinUlp(util::Float16 left, util::Float16 right, int32_t n_ulps);
ARROW_TESTING_EXPORT
-bool WithinUlp(float left, float right, int n_ulps);
+void AssertWithinUlp(float left, float right, int32_t n_ulps);
ARROW_TESTING_EXPORT
-bool WithinUlp(double left, double right, int n_ulps);
-
-ARROW_TESTING_EXPORT
-void AssertWithinUlp(util::Float16 left, util::Float16 right, int n_ulps);
-ARROW_TESTING_EXPORT
-void AssertWithinUlp(float left, float right, int n_ulps);
-ARROW_TESTING_EXPORT
-void AssertWithinUlp(double left, double right, int n_ulps);
+void AssertWithinUlp(double left, double right, int32_t n_ulps);
} // namespace arrow
diff --git a/cpp/src/arrow/util/CMakeLists.txt
b/cpp/src/arrow/util/CMakeLists.txt
index 8b05b4b6e6..28ea215bd2 100644
--- a/cpp/src/arrow/util/CMakeLists.txt
+++ b/cpp/src/arrow/util/CMakeLists.txt
@@ -81,6 +81,7 @@ add_arrow_test(utility-test
tracing_test.cc
trie_test.cc
uri_test.cc
+ ulp_distance_test.cc
utf8_util_test.cc
value_parsing_test.cc
EXTRA_LINK_LIBS
diff --git a/cpp/src/arrow/util/meson.build b/cpp/src/arrow/util/meson.build
index e21537b0d4..c39e09c2d0 100644
--- a/cpp/src/arrow/util/meson.build
+++ b/cpp/src/arrow/util/meson.build
@@ -216,6 +216,7 @@ utility_test_srcs = [
'time_test.cc',
'tracing_test.cc',
'trie_test.cc',
+ 'ulp_distance_test.cc',
'uri_test.cc',
'utf8_util_test.cc',
'value_parsing_test.cc',
diff --git a/cpp/src/arrow/testing/math.cc b/cpp/src/arrow/util/ulp_distance.cc
similarity index 64%
copy from cpp/src/arrow/testing/math.cc
copy to cpp/src/arrow/util/ulp_distance.cc
index 79f7ec3033..cbc6e92090 100644
--- a/cpp/src/arrow/testing/math.cc
+++ b/cpp/src/arrow/util/ulp_distance.cc
@@ -15,20 +15,18 @@
// specific language governing permissions and limitations
// under the License.
-#include "arrow/testing/math.h"
+#include "arrow/util/ulp_distance_internal.h"
#include <algorithm>
+#include <cinttypes>
#include <cmath>
-#include <limits>
#include <type_traits>
-#include <gtest/gtest.h>
-
#include "arrow/util/float16.h"
#include "arrow/util/logging_internal.h"
#include "arrow/util/ubsan.h"
-namespace arrow {
+namespace arrow::internal {
namespace {
template <typename Float>
@@ -52,7 +50,7 @@ struct FloatToUInt<util::Float16> {
template <typename Float>
struct UlpDistanceUtil {
public:
- using UIntType = typename FloatToUInt<Float>::Type;
+ using UIntType = FloatToUInt<Float>::Type;
static constexpr UIntType kNumberOfBits = sizeof(Float) * 8;
static constexpr UIntType kSignMask = static_cast<UIntType>(1) <<
(kNumberOfBits - 1);
@@ -66,6 +64,8 @@ struct UlpDistanceUtil {
if (biased_left > biased_right) {
std::swap(biased_left, biased_right);
}
+
+ // Handling of NaN should be determined by the comparison policy.
return biased_right - biased_left;
}
@@ -82,62 +82,24 @@ struct UlpDistanceUtil {
};
template <typename Float>
-bool WithinUlpGeneric(Float left, Float right, int n_ulps) {
- if constexpr (std::is_same_v<Float, util::Float16>) {
- if (left.is_nan() || right.is_nan()) {
- return left.is_nan() == right.is_nan();
- } else if (left.is_infinity() || right.is_infinity()) {
- return left == right;
- }
- } else {
- if (std::isnan(left) || std::isnan(right)) {
- return std::isnan(left) == std::isnan(right);
- }
- if (!std::isfinite(left) || !std::isfinite(right)) {
- return left == right;
- }
- }
-
- if (n_ulps == 0) {
- return left == right;
- }
-
- DCHECK_GE(n_ulps, 1);
+bool WithinUlpGeneric(Float left, Float right, int32_t n_ulps) {
+ DCHECK_GE(n_ulps, 0);
return UlpDistanceUtil<Float>::UlpDistance(left, right) <=
static_cast<uint64_t>(n_ulps);
}
-template <typename Float>
-void AssertWithinUlpGeneric(Float left, Float right, int n_ulps) {
- if (!WithinUlpGeneric(left, right, n_ulps)) {
- FAIL() << left << " and " << right << " are not within " << n_ulps << "
ulps";
- }
-}
-
} // namespace
-bool WithinUlp(util::Float16 left, util::Float16 right, int n_ulps) {
+bool WithinUlp(util::Float16 left, util::Float16 right, int32_t n_ulps) {
return WithinUlpGeneric(left, right, n_ulps);
}
-bool WithinUlp(float left, float right, int n_ulps) {
+bool WithinUlp(float left, float right, int32_t n_ulps) {
return WithinUlpGeneric(left, right, n_ulps);
}
-bool WithinUlp(double left, double right, int n_ulps) {
+bool WithinUlp(double left, double right, int32_t n_ulps) {
return WithinUlpGeneric(left, right, n_ulps);
}
-void AssertWithinUlp(util::Float16 left, util::Float16 right, int n_ulps) {
- AssertWithinUlpGeneric(left, right, n_ulps);
-}
-
-void AssertWithinUlp(float left, float right, int n_ulps) {
- AssertWithinUlpGeneric(left, right, n_ulps);
-}
-
-void AssertWithinUlp(double left, double right, int n_ulps) {
- AssertWithinUlpGeneric(left, right, n_ulps);
-}
-
-} // namespace arrow
+} // namespace arrow::internal
diff --git a/cpp/src/arrow/testing/math.h
b/cpp/src/arrow/util/ulp_distance_internal.h
similarity index 59%
copy from cpp/src/arrow/testing/math.h
copy to cpp/src/arrow/util/ulp_distance_internal.h
index 1e829e0d61..c6661d2198 100644
--- a/cpp/src/arrow/testing/math.h
+++ b/cpp/src/arrow/util/ulp_distance_internal.h
@@ -17,23 +17,18 @@
#pragma once
-#include "arrow/testing/visibility.h"
#include "arrow/type_fwd.h"
+#include "arrow/util/visibility.h"
-namespace arrow {
+namespace arrow::internal {
-ARROW_TESTING_EXPORT
-bool WithinUlp(util::Float16 left, util::Float16 right, int n_ulps);
-ARROW_TESTING_EXPORT
-bool WithinUlp(float left, float right, int n_ulps);
-ARROW_TESTING_EXPORT
-bool WithinUlp(double left, double right, int n_ulps);
+ARROW_EXPORT
+bool WithinUlp(util::Float16 left, util::Float16 right, int32_t n_ulps);
-ARROW_TESTING_EXPORT
-void AssertWithinUlp(util::Float16 left, util::Float16 right, int n_ulps);
-ARROW_TESTING_EXPORT
-void AssertWithinUlp(float left, float right, int n_ulps);
-ARROW_TESTING_EXPORT
-void AssertWithinUlp(double left, double right, int n_ulps);
+ARROW_EXPORT
+bool WithinUlp(float left, float right, int32_t n_ulps);
-} // namespace arrow
+ARROW_EXPORT
+bool WithinUlp(double left, double right, int32_t n_ulps);
+
+} // namespace arrow::internal
diff --git a/cpp/src/arrow/testing/gtest_util_test.cc
b/cpp/src/arrow/util/ulp_distance_test.cc
similarity index 52%
copy from cpp/src/arrow/testing/gtest_util_test.cc
copy to cpp/src/arrow/util/ulp_distance_test.cc
index 2a28df79dd..e2c84e25f3 100644
--- a/cpp/src/arrow/testing/gtest_util_test.cc
+++ b/cpp/src/arrow/util/ulp_distance_test.cc
@@ -15,173 +15,24 @@
// specific language governing permissions and limitations
// under the License.
+#include "arrow/util/ulp_distance_internal.h"
+
+#include <cinttypes>
#include <cmath>
-#include <memory>
#include <type_traits>
#include <vector>
-#include <gtest/gtest-spi.h>
-#include <gtest/gtest.h>
+#include "gtest/gtest.h"
-#include "arrow/array.h"
-#include "arrow/array/builder_decimal.h"
-#include "arrow/datum.h"
-#include "arrow/record_batch.h"
-#include "arrow/table.h"
-#include "arrow/tensor.h"
#include "arrow/testing/gtest_util.h"
-#include "arrow/testing/math.h"
-#include "arrow/testing/random.h"
-#include "arrow/type.h"
-#include "arrow/type_traits.h"
-#include "arrow/util/checked_cast.h"
#include "arrow/util/float16.h"
-namespace arrow::util {
-
-// Test basic cases for contains NaN.
-class TestAssertContainsNaN : public ::testing::Test {};
-
-TEST_F(TestAssertContainsNaN, BatchesEqual) {
- auto schema = ::arrow::schema({
- {field("a", float32())},
- {field("b", float64())},
- });
-
- auto expected = RecordBatchFromJSON(schema,
- R"([{"a": 3, "b": 5},
- {"a": 1, "b": 3},
- {"a": 3, "b": 4},
- {"a": NaN, "b": 6},
- {"a": 2, "b": 5},
- {"a": 1, "b": NaN},
- {"a": 1, "b": 3}
- ])");
- auto actual = RecordBatchFromJSON(schema,
- R"([{"a": 3, "b": 5},
- {"a": 1, "b": 3},
- {"a": 3, "b": 4},
- {"a": NaN, "b": 6},
- {"a": 2, "b": 5},
- {"a": 1, "b": NaN},
- {"a": 1, "b": 3}
- ])");
- ASSERT_BATCHES_EQUAL(*expected, *actual);
- AssertBatchesApproxEqual(*expected, *actual);
-}
-
-TEST_F(TestAssertContainsNaN, TableEqual) {
- auto schema = ::arrow::schema({
- {field("a", float32())},
- {field("b", float64())},
- });
-
- auto expected = TableFromJSON(schema, {R"([{"a": null, "b": 5},
- {"a": NaN, "b": 3},
- {"a": 3, "b": null}
- ])",
- R"([{"a": null, "b": null},
- {"a": 2, "b": NaN},
- {"a": 1, "b": 5},
- {"a": 3, "b": 5}
- ])"});
- auto actual = TableFromJSON(schema, {R"([{"a": null, "b": 5},
- {"a": NaN, "b": 3},
- {"a": 3, "b": null}
- ])",
- R"([{"a": null, "b": null},
- {"a": 2, "b": NaN},
- {"a": 1, "b": 5},
- {"a": 3, "b": 5}
- ])"});
- ASSERT_TABLES_EQUAL(*expected, *actual);
-}
-
-TEST_F(TestAssertContainsNaN, ArrayEqual) {
- auto expected = ArrayFromJSON(float64(), "[0, 1, 2, NaN]");
- auto actual = ArrayFromJSON(float64(), "[0, 1, 2, NaN]");
- AssertArraysEqual(*expected, *actual);
-}
-
-TEST_F(TestAssertContainsNaN, ChunkedEqual) {
- auto expected = ChunkedArrayFromJSON(float64(), {
- "[null, 1]",
- "[3, NaN, 2]",
- "[NaN]",
- });
-
- auto actual = ChunkedArrayFromJSON(float64(), {
- "[null, 1]",
- "[3, NaN, 2]",
- "[NaN]",
- });
- AssertChunkedEqual(*expected, *actual);
-}
-
-TEST_F(TestAssertContainsNaN, DatumEqual) {
- // scalar
- auto expected_scalar = ScalarFromJSON(float64(), "NaN");
- auto actual_scalar = ScalarFromJSON(float64(), "NaN");
- AssertDatumsEqual(expected_scalar, actual_scalar);
-
- // array
- auto expected_array = ArrayFromJSON(float64(), "[3, NaN, 2, 1, 5]");
- auto actual_array = ArrayFromJSON(float64(), "[3, NaN, 2, 1, 5]");
- AssertDatumsEqual(expected_array, actual_array);
+namespace arrow::internal {
- // chunked array
- auto expected_chunked = ChunkedArrayFromJSON(float64(), {
- "[null, 1]",
- "[3, NaN, 2]",
- "[NaN]",
- });
-
- auto actual_chunked = ChunkedArrayFromJSON(float64(), {
- "[null, 1]",
- "[3, NaN, 2]",
- "[NaN]",
- });
- AssertDatumsEqual(expected_chunked, actual_chunked);
-}
-
-class TestTensorFromJSON : public ::testing::Test {};
-
-TEST_F(TestTensorFromJSON, FromJSONAndArray) {
- std::vector<int64_t> shape = {9, 2};
- const int64_t i64_size = sizeof(int64_t);
- std::vector<int64_t> f_strides = {i64_size, i64_size * shape[0]};
- std::vector<int64_t> f_values = {1, 2, 3, 4, 5, 6, 7, 8, 9,
- 10, 20, 30, 40, 50, 60, 70, 80, 90};
- auto data = Buffer::Wrap(f_values);
-
- std::shared_ptr<Tensor> tensor_expected;
- ASSERT_OK_AND_ASSIGN(tensor_expected, Tensor::Make(int64(), data, shape,
f_strides));
-
- std::shared_ptr<Tensor> result = TensorFromJSON(
- int64(), "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80,
90]",
- shape, f_strides);
-
- EXPECT_TRUE(tensor_expected->Equals(*result));
-}
-
-TEST_F(TestTensorFromJSON, FromJSON) {
- std::vector<int64_t> shape = {9, 2};
- std::vector<int64_t> values = {1, 2, 3, 4, 5, 6, 7, 8, 9,
- 10, 20, 30, 40, 50, 60, 70, 80, 90};
- auto data = Buffer::Wrap(values);
-
- std::shared_ptr<Tensor> tensor_expected;
- ASSERT_OK_AND_ASSIGN(tensor_expected, Tensor::Make(int64(), data, shape));
-
- std::shared_ptr<Tensor> result = TensorFromJSON(
- int64(), "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80,
90]",
- "[9, 2]");
-
- EXPECT_TRUE(tensor_expected->Equals(*result));
-}
+using util::Float16;
template <typename Float>
-void CheckWithinUlpSingle(Float x, Float y, int n_ulp) {
+void CheckWithinUlpSingle(Float x, Float y, int32_t n_ulp) {
ARROW_SCOPED_TRACE("x = ", x, ", y = ", y, ", n_ulp = ", n_ulp);
ASSERT_TRUE(WithinUlp(x, y, n_ulp));
}
@@ -357,18 +208,4 @@ TEST(TestWithinUlp, Float16) {
CheckNotWithinUlp(Float16(12.34f), Float16(-12.34f), 10);
}
-TEST(AssertTestWithinUlp, Basics) {
- AssertWithinUlp(123.4567, 123.45670000000015, 11);
- AssertWithinUlp(123.456f, 123.456085f, 11);
- AssertWithinUlp(Float16(123.456f), Float16(124.143501f), 11);
-#ifndef _WIN32
- // GH-47442
- EXPECT_FATAL_FAILURE(AssertWithinUlp(123.4567, 123.45670000000015, 10),
- "not within 10 ulps");
- EXPECT_FATAL_FAILURE(AssertWithinUlp(123.456f, 123.456085f, 10), "not within
10 ulps");
- EXPECT_FATAL_FAILURE(AssertWithinUlp(Float16(123.456f),
Float16(124.143501f), 10),
- "not within 10 ulps");
-#endif
-}
-
-} // namespace arrow::util
+} // namespace arrow::internal