Skip to content

Commit

Permalink
GH-XXX: [C++] Add WithinUlp testing functions
Browse files Browse the repository at this point in the history
  • Loading branch information
pitrou committed Dec 2, 2024
1 parent 88c704e commit 5ec090d
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 0 deletions.
1 change: 1 addition & 0 deletions cpp/src/arrow/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,7 @@ set(ARROW_TESTING_SRCS
testing/fixed_width_test_util.cc
testing/generator.cc
testing/gtest_util.cc
testing/math.cc
testing/process.cc
testing/random.cc
testing/util.cc)
Expand Down
62 changes: 62 additions & 0 deletions cpp/src/arrow/testing/gtest_util_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
// specific language governing permissions and limitations
// under the License.

#include <cmath>

#include <gtest/gtest.h>

#include "arrow/array.h"
Expand All @@ -23,6 +25,7 @@
#include "arrow/record_batch.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"
Expand Down Expand Up @@ -171,4 +174,63 @@ 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 = ", x, ", n_ulp = ", n_ulp);
ASSERT_TRUE(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);

for (int exp : {1, -1, 10, -10}) {
Float x_scaled = std::ldexp(x, exp);
Float 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) {
ARROW_SCOPED_TRACE("x = ", x, ", y = ", x, ", n_ulp = ", n_ulp);
ASSERT_FALSE(WithinUlp(x, y, n_ulp));
ASSERT_FALSE(WithinUlp(y, x, n_ulp));
if (n_ulp > 1) {
ASSERT_FALSE(WithinUlp(x, y, n_ulp - 1));
ASSERT_FALSE(WithinUlp(y, x, n_ulp - 1));
}

for (int exp : {1, -1, 10, -10}) {
Float x_scaled = std::ldexp(x, exp);
Float y_scaled = std::ldexp(y, exp);
ASSERT_FALSE(WithinUlp(x_scaled, y_scaled, n_ulp));
ASSERT_FALSE(WithinUlp(y_scaled, x_scaled, n_ulp));
}
}

TEST(TestWithinUlp, Double) {
for (double f : {0.0, 1e-20, 1.0, 2345678.9}) {
CheckWithinUlp(f, f, 1);
CheckWithinUlp(f, f, 42);
}
CheckWithinUlp(1.0, 1.0000000000000002, 1);
CheckWithinUlp(1.0, 1.0000000000000007, 3);
CheckNotWithinUlp(1.0, 1.0000000000000007, 2);
CheckNotWithinUlp(1.0, 1.0000000000000007, 1);

CheckWithinUlp(123.4567, 123.45670000000015, 11);
CheckNotWithinUlp(123.4567, 123.45670000000015, 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);
}

} // namespace arrow
77 changes: 77 additions & 0 deletions cpp/src/arrow/testing/math.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

#include "arrow/testing/math.h"

#include <cmath>
#include <limits>

#include "arrow/util/logging.h"

namespace arrow {
namespace {

template <typename Float>
constexpr Float kOneUlp;
template <>
constexpr float kOneUlp<float> = 5.9604645e-08f;
template <>
constexpr float kOneUlp<double> = 1.1102230246251565e-16;

template <typename Float>
bool WithinUlpOneWay(Float left, Float right, int n_ulp) {
// Ideally these would be compile-time (constexpr) checks
DCHECK_LT(Float(1.0) - kOneUlp<Float>, Float(1.0));
DCHECK_EQ(std::nextafter(Float(1.0) - kOneUlp<Float>, Float(1.0)), Float(1.0));

DCHECK_GE(n_ulp, 1);

if (left == 0) {
return left == right;
}
if (left < 0) {
left = -left;
right = -right;
}

int left_exp;
Float left_mant = std::frexp(left, &left_exp);
Float delta = static_cast<Float>(n_ulp) * kOneUlp<Float>;
Float lower_bound = std::ldexp(left_mant - delta, left_exp);
Float upper_bound = std::ldexp(left_mant + delta, left_exp);
return right >= lower_bound && right <= upper_bound;
}

template <typename Float>
bool WithinUlpGeneric(Float left, Float right, int n_ulp) {
if (!std::isfinite(left) || !std::isfinite(right)) {
return left == right;
}
return WithinUlpOneWay(left, right, n_ulp) || WithinUlpOneWay(right, left, n_ulp);
}

} // namespace

bool WithinUlp(float left, float right, int n_ulp) {
return WithinUlpGeneric(left, right, n_ulp);
}

bool WithinUlp(double left, double right, int n_ulp) {
return WithinUlpGeneric(left, right, n_ulp);
}

} // namespace arrow
30 changes: 30 additions & 0 deletions cpp/src/arrow/testing/math.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

#pragma once

#include "arrow/testing/visibility.h"

namespace arrow {

ARROW_TESTING_EXPORT
bool WithinUlp(float left, float right, int n_ulp);

ARROW_TESTING_EXPORT
bool WithinUlp(double left, double right, int n_ulp);

} // namespace arrow

0 comments on commit 5ec090d

Please sign in to comment.