Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions include/rabitqlib/utils/input_checks.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ static inline bool all_finite(const T* values, size_t count) {

// Largest coordinate magnitude whose squared distances, their products in graph
// pruning, and rotated residuals stay inside float32; shared with clustering.
// dim == 0 has no such limit; FLT_MAX avoids dividing by zero and still leaves
// +/-Inf out of range.
static inline float max_coordinate_magnitude(size_t dim) {
if (dim == 0) {
return std::numeric_limits<float>::max();
}
return static_cast<float>(std::sqrt(
std::sqrt(static_cast<double>(std::numeric_limits<float>::max())) /
(64.0 * static_cast<double>(dim))
Expand All @@ -49,15 +54,17 @@ static inline float max_coordinate_magnitude(size_t dim) {
static inline void check_coordinates(
const float* values, size_t count, size_t dim, const char* what
) {
if (!all_finite(values, count)) {
throw std::invalid_argument(std::string(what) + " must contain only finite values");
}
const float bound = max_coordinate_magnitude(dim);
size_t out_of_range = 0;
for (size_t i = 0; i < count; ++i) {
out_of_range += static_cast<size_t>(!(std::fabs(values[i]) <= bound));
}
if (out_of_range != 0) {
if (!all_finite(values, count)) {
throw std::invalid_argument(
std::string(what) + " must contain only finite values"
);
}
throw std::invalid_argument(
std::string(what) + " exceeds the safe float32 coordinate range"
);
Expand Down
59 changes: 53 additions & 6 deletions tests/unit/rabitqlib/utils/input_checks_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@
namespace rabitqlib {
namespace {

template <typename Call>
void expect_message(const Call& call, const std::string& message) {
try {
call();
ADD_FAILURE() << "expected: " << message;
} catch (const std::invalid_argument& error) { EXPECT_EQ(error.what(), message); }
}

TEST(InputChecksTest, DetectsEveryNonFiniteValueForFloatAndDouble) {
const std::vector<float> finite{
0.0F,
Expand Down Expand Up @@ -59,12 +67,6 @@ TEST(InputChecksTest, CoordinateBoundMatchesClusteringAndAcceptsItself) {
EXPECT_NO_THROW(check_coordinates(values.data(), values.size(), kDim, "data"));
EXPECT_NO_THROW(check_coordinates(values.data(), 0, kDim, "data"));

const auto expect_message = [](const auto& call, const std::string& message) {
try {
call();
ADD_FAILURE() << "expected: " << message;
} catch (const std::invalid_argument& error) { EXPECT_EQ(error.what(), message); }
};
values[kDim - 1] = std::nextafter(bound, std::numeric_limits<float>::infinity());
expect_message(
[&] { check_coordinates(values.data(), values.size(), kDim, "data"); },
Expand All @@ -88,5 +90,50 @@ TEST(InputChecksTest, CoordinateBoundMatchesClusteringAndAcceptsItself) {
);
}

TEST(InputChecksTest, NonFiniteWinsOverOversizedFiniteValues) {
constexpr size_t kDim = 128;
const float bound = max_coordinate_magnitude(kDim);
const float oversized = std::nextafter(bound, std::numeric_limits<float>::infinity());
for (float non_finite :
{std::numeric_limits<float>::quiet_NaN(),
std::numeric_limits<float>::signaling_NaN(),
std::numeric_limits<float>::infinity(),
-std::numeric_limits<float>::infinity()}) {
// The oversized finite value precedes the non-finite value.
std::vector<float> values(3, 0.25F);
values[0] = oversized;
values[2] = non_finite;
expect_message(
[&] { check_coordinates(values.data(), values.size(), kDim, "data"); },
"data must contain only finite values"
);
// The non-finite value precedes the oversized finite value.
values[0] = non_finite;
values[2] = oversized;
expect_message(
[&] { check_coordinates(values.data(), values.size(), kDim, "data"); },
"data must contain only finite values"
);
}
}

TEST(InputChecksTest, ZeroDimensionRejectsOnlyNonFiniteValues) {
// dim == 0 has no range limit; every finite value is accepted.
EXPECT_EQ(max_coordinate_magnitude(0), std::numeric_limits<float>::max());
std::vector<float> values{
std::numeric_limits<float>::max(), -std::numeric_limits<float>::max(), 0.25F};
EXPECT_NO_THROW(check_coordinates(values.data(), values.size(), 0, "data"));
for (float non_finite :
{std::numeric_limits<float>::quiet_NaN(),
std::numeric_limits<float>::infinity(),
-std::numeric_limits<float>::infinity()}) {
values[1] = non_finite;
expect_message(
[&] { check_coordinates(values.data(), values.size(), 0, "data"); },
"data must contain only finite values"
);
}
}

} // namespace
} // namespace rabitqlib
Loading