[libc-commits] [libc] [libc][math] Add Generic Comparison Operations for floating point types (PR #144983)
via libc-commits
libc-commits at lists.llvm.org
Thu Jun 19 21:54:41 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libc
Author: Krishna Pandey (krishna2803)
<details>
<summary>Changes</summary>
The PR implements the following generic comparison operation functions for floating point types along with unittests:
- `fputil::equals`
- `fputil::not_equals`
- `fputil::less_than`
- `fputil::less_than_or_equals`
- `fputil::greater_than`
- `fputil::greater_than_or_equals`
cc @<!-- -->lntue @<!-- -->overmighty
---
Patch is 23.44 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/144983.diff
4 Files Affected:
- (modified) libc/src/__support/FPUtil/CMakeLists.txt (+11)
- (added) libc/src/__support/FPUtil/ComparisonOperations.h (+122)
- (modified) libc/test/src/__support/FPUtil/CMakeLists.txt (+12)
- (added) libc/test/src/__support/FPUtil/comparison_operations_test.cpp (+254)
``````````diff
diff --git a/libc/src/__support/FPUtil/CMakeLists.txt b/libc/src/__support/FPUtil/CMakeLists.txt
index bfe0170f09fd9..ce068df8d4c03 100644
--- a/libc/src/__support/FPUtil/CMakeLists.txt
+++ b/libc/src/__support/FPUtil/CMakeLists.txt
@@ -209,6 +209,17 @@ add_header_library(
libc.src.__support.macros.properties.types
)
+add_header_library(
+ comparison_operations
+ HDRS
+ ComparisonOperations.h
+ DEPENDS
+ .fp_bits
+ .fenv_impl
+ libc.src.__support.CPP.type_traits
+ libc.src.__support.config
+)
+
add_header_library(
hypot
HDRS
diff --git a/libc/src/__support/FPUtil/ComparisonOperations.h b/libc/src/__support/FPUtil/ComparisonOperations.h
new file mode 100644
index 0000000000000..cf71a14df0465
--- /dev/null
+++ b/libc/src/__support/FPUtil/ComparisonOperations.h
@@ -0,0 +1,122 @@
+//===-- Comparison operations on floating point numbers --------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_FPUTIL_COMPARISONOPERATIONS_H
+#define LLVM_LIBC_SRC___SUPPORT_FPUTIL_COMPARISONOPERATIONS_H
+
+#include "FEnvImpl.h" // raise_except_if_required
+#include "FPBits.h" // FPBits<T>
+#include "src/__support/CPP/type_traits.h" // enable_if, is_floating_point
+#include "src/__support/macros/config.h" // LIBC_NAMESPACE_DECL
+
+namespace LIBC_NAMESPACE_DECL {
+namespace fputil {
+
+// IEEE Standard 754-2019. Section 5.11
+// Rules for comparison within the same floating point type
+// 1. +0 = −0
+// 2. (i) +inf = +inf
+// (ii) -inf = -inf
+// (iii) -inf != +inf
+// 3. Any comparison with NaN return false except (NaN != NaN => true)
+template <typename T>
+LIBC_INLINE cpp::enable_if_t<cpp::is_floating_point_v<T>, bool> equals(T x,
+ T y) {
+ using FPBits = FPBits<T>;
+ FPBits x_bits(x);
+ FPBits y_bits(y);
+
+ if (x_bits.is_signaling_nan() || y_bits.is_signaling_nan())
+ fputil::raise_except_if_required(FE_INVALID);
+
+ // NaN == x returns false for every x
+ if (x_bits.is_nan() || y_bits.is_nan())
+ return false;
+
+ // +/- 0 == +/- 0
+ if (x_bits.is_zero() && y_bits.is_zero())
+ return true;
+
+ // should also work for comparisons of different signs
+ return x_bits.uintval() == y_bits.uintval();
+}
+
+// !(x == y) => x != y
+template <typename T>
+LIBC_INLINE cpp::enable_if_t<cpp::is_floating_point_v<T>, bool>
+not_equals(T x, T y) {
+ return !equals(x, y);
+}
+
+// Rules:
+// 1. -inf < x (x != -inf)
+// 2. x < +inf (x != +inf)
+// 3. Any comparison with NaN return false
+template <typename T>
+LIBC_INLINE cpp::enable_if_t<cpp::is_floating_point_v<T>, bool> less_than(T x,
+ T y) {
+ using FPBits = FPBits<T>;
+ FPBits x_bits(x);
+ FPBits y_bits(y);
+
+ if (x_bits.is_signaling_nan() || y_bits.is_signaling_nan())
+ fputil::raise_except_if_required(FE_INVALID);
+
+ // Any comparison with NaN returns false
+ if (x_bits.is_nan() || y_bits.is_nan())
+ return false;
+
+ if (x_bits.is_zero() && y_bits.is_zero())
+ return false;
+
+ if (x_bits.is_neg() && y_bits.is_pos())
+ return true;
+
+ if (x_bits.is_pos() && y_bits.is_neg())
+ return false;
+
+ // since we store the float in the format: s | e | m
+ // the comparisons should work if we directly compare the uintval's
+
+ // TODO: verify if we should use FPBits.get_exponent and FPBits.get_mantissa
+ // instead of directly comparing uintval's
+
+ // both negative
+ if (x_bits.is_neg())
+ return x_bits.uintval() > y_bits.uintval();
+
+ // both positive
+ return x_bits.uintval() < y_bits.uintval();
+}
+
+// x < y => y > x
+template <typename T>
+LIBC_INLINE cpp::enable_if_t<cpp::is_floating_point_v<T>, bool>
+greater_than(T x, T y) {
+ return less_than(y, x);
+}
+
+// following is expression is correct, accounting for NaN case(s) as well
+// x <= y => (x < y) || (x == y)
+template <typename T>
+LIBC_INLINE cpp::enable_if_t<cpp::is_floating_point_v<T>, bool>
+less_than_or_equals(T x, T y) {
+ return less_than(x, y) || equals(x, y);
+}
+
+// x >= y => (x > y) || (x == y) => (y < x) || (x == y)
+template <typename T>
+LIBC_INLINE cpp::enable_if_t<cpp::is_floating_point_v<T>, bool>
+greater_than_or_equals(T x, T y) {
+ return less_than(y, x) || equals(x, y);
+}
+
+} // namespace fputil
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_FPUTIL_COMPARISONOPERATIONS_H
diff --git a/libc/test/src/__support/FPUtil/CMakeLists.txt b/libc/test/src/__support/FPUtil/CMakeLists.txt
index 1e64e9ba425a5..1dcd666ec3d25 100644
--- a/libc/test/src/__support/FPUtil/CMakeLists.txt
+++ b/libc/test/src/__support/FPUtil/CMakeLists.txt
@@ -38,3 +38,15 @@ add_fp_unittest(
DEPENDS
libc.src.__support.FPUtil.rounding_mode
)
+
+add_fp_unittest(
+ comparison_operations_test
+ SUITE
+ libc-fputil-tests
+ SRCS
+ comparison_operations_test.cpp
+ DEPENDS
+ libc.src.__support.FPUtil.fp_bits
+ # libc.src.__support.FPUtil.comparison_operations
+)
+
diff --git a/libc/test/src/__support/FPUtil/comparison_operations_test.cpp b/libc/test/src/__support/FPUtil/comparison_operations_test.cpp
new file mode 100644
index 0000000000000..94c234446ad8e
--- /dev/null
+++ b/libc/test/src/__support/FPUtil/comparison_operations_test.cpp
@@ -0,0 +1,254 @@
+//===-- Unittests for Comparison Operations for FPBits class -------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/__support/FPUtil/ComparisonOperations.h"
+#include "src/__support/FPUtil/FPBits.h"
+#include "src/__support/macros/properties/types.h"
+#include "src/__support/sign.h"
+#include "test/UnitTest/FPMatcher.h"
+#include "test/UnitTest/Test.h"
+#include "utils/MPFRWrapper/MPFRUtils.h"
+
+using LIBC_NAMESPACE::fputil::equals;
+using LIBC_NAMESPACE::fputil::greater_than;
+using LIBC_NAMESPACE::fputil::greater_than_or_equals;
+using LIBC_NAMESPACE::fputil::less_than;
+using LIBC_NAMESPACE::fputil::less_than_or_equals;
+using LIBC_NAMESPACE::fputil::not_equals;
+
+// FIXME: currently i have used NAN here
+// need to find a better way to get a NAN floating point type
+// - need to see if FPRep could be used?
+
+#define TEST_EQUALS(Name, Type) \
+ TEST(LlvmLibc##Name##ComparisionOperationsTest, Equals) { \
+ using Bits = LIBC_NAMESPACE::fputil::FPBits<Type>; \
+ Type pos_zero = Bits::zero().get_val(); \
+ Type neg_zero = -pos_zero; \
+ Type pos_inf = Bits::inf().get_val(); \
+ Type neg_inf = Bits::inf(Sign::NEG).get_val(); \
+ Type nan = NAN; \
+ Type pos_normal = Type(3.14); \
+ Type neg_normal = Type(-2.71); \
+ Type pos_large = Type(1000000.0); \
+ Type neg_large = Type(-1000000.0); \
+ \
+ EXPECT_TRUE(equals(pos_zero, pos_zero)); \
+ EXPECT_TRUE(equals(neg_zero, neg_zero)); \
+ EXPECT_TRUE(equals(pos_inf, pos_inf)); \
+ EXPECT_TRUE(equals(neg_inf, neg_inf)); \
+ EXPECT_TRUE(equals(pos_normal, pos_normal)); \
+ EXPECT_TRUE(equals(neg_normal, neg_normal)); \
+ \
+ EXPECT_TRUE(equals(pos_zero, neg_zero)); \
+ EXPECT_TRUE(equals(neg_zero, pos_zero)); \
+ \
+ EXPECT_FALSE(equals(pos_normal, neg_normal)); \
+ EXPECT_FALSE(equals(pos_normal, pos_large)); \
+ EXPECT_FALSE(equals(pos_inf, neg_inf)); \
+ EXPECT_FALSE(equals(pos_inf, pos_normal)); \
+ EXPECT_FALSE(equals(neg_inf, neg_normal)); \
+ EXPECT_FALSE(equals(pos_large, neg_large)); \
+ \
+ EXPECT_FALSE(equals(nan, nan)); \
+ EXPECT_FALSE(equals(nan, pos_normal)); \
+ EXPECT_FALSE(equals(nan, pos_zero)); \
+ EXPECT_FALSE(equals(nan, pos_inf)); \
+ EXPECT_FALSE(equals(pos_normal, nan)); \
+ }
+
+#define TEST_NOT_EQUALS(Name, Type) \
+ TEST(LlvmLibc##Name##ComparisionOperationsTest, NotEquals) { \
+ using Bits = LIBC_NAMESPACE::fputil::FPBits<Type>; \
+ Type pos_zero = Bits::zero().get_val(); \
+ Type neg_zero = Bits::zero(Sign::NEG).get_val(); \
+ Type pos_inf = Bits::inf().get_val(); \
+ Type neg_inf = Bits::inf(Sign::NEG).get_val(); \
+ Type nan = NAN; \
+ Type pos_normal = Type(3.14); \
+ Type neg_normal = Type(-2.71); \
+ Type pos_large = Type(1000000.0); \
+ Type neg_large = Type(-1000000.0); \
+ \
+ EXPECT_FALSE(not_equals(pos_zero, pos_zero)); \
+ EXPECT_FALSE(not_equals(pos_zero, neg_zero)); \
+ EXPECT_FALSE(not_equals(pos_inf, pos_inf)); \
+ EXPECT_FALSE(not_equals(neg_inf, neg_inf)); \
+ EXPECT_FALSE(not_equals(pos_normal, pos_normal)); \
+ \
+ EXPECT_TRUE(not_equals(pos_normal, neg_normal)); \
+ EXPECT_TRUE(not_equals(pos_inf, neg_inf)); \
+ EXPECT_TRUE(not_equals(pos_normal, pos_zero)); \
+ EXPECT_TRUE(not_equals(pos_large, neg_large)); \
+ EXPECT_TRUE(not_equals(pos_inf, pos_normal)); \
+ \
+ EXPECT_TRUE(not_equals(nan, nan)); \
+ EXPECT_TRUE(not_equals(nan, pos_normal)); \
+ EXPECT_TRUE(not_equals(nan, pos_zero)); \
+ EXPECT_TRUE(not_equals(nan, pos_inf)); \
+ EXPECT_TRUE(not_equals(pos_normal, nan)); \
+ }
+
+#define TEST_LESS_THAN(Name, Type) \
+ TEST(LlvmLibc##Name##ComparisionOperationsTest, LessThan) { \
+ using Bits = LIBC_NAMESPACE::fputil::FPBits<Type>; \
+ Type pos_zero = Bits::zero().get_val(); \
+ Type neg_zero = -pos_zero; \
+ Type pos_inf = Bits::inf().get_val(); \
+ Type neg_inf = Bits::inf(Sign::NEG).get_val(); \
+ Type nan = NAN; \
+ Type pos_small = Type(0.1); \
+ Type neg_small = Type(-0.1); \
+ Type pos_large = Type(1000000.0); \
+ Type neg_large = Type(-1000000.0); \
+ \
+ EXPECT_TRUE(less_than(neg_small, pos_small)); \
+ EXPECT_TRUE(less_than(pos_small, pos_large)); \
+ EXPECT_TRUE(less_than(neg_large, neg_small)); \
+ EXPECT_FALSE(less_than(pos_large, pos_small)); \
+ EXPECT_FALSE(less_than(pos_small, neg_small)); \
+ \
+ EXPECT_FALSE(less_than(pos_zero, neg_zero)); \
+ EXPECT_FALSE(less_than(neg_zero, pos_zero)); \
+ EXPECT_FALSE(less_than(pos_zero, pos_zero)); \
+ \
+ EXPECT_TRUE(less_than(neg_small, pos_zero)); \
+ EXPECT_TRUE(less_than(neg_zero, pos_small)); \
+ EXPECT_FALSE(less_than(pos_small, pos_zero)); \
+ \
+ EXPECT_TRUE(less_than(neg_inf, pos_inf)); \
+ EXPECT_TRUE(less_than(neg_inf, neg_small)); \
+ EXPECT_TRUE(less_than(pos_small, pos_inf)); \
+ EXPECT_FALSE(less_than(pos_inf, pos_small)); \
+ \
+ EXPECT_FALSE(less_than(pos_small, pos_small)); \
+ EXPECT_FALSE(less_than(neg_inf, neg_inf)); \
+ \
+ EXPECT_FALSE(less_than(nan, pos_small)); \
+ EXPECT_FALSE(less_than(pos_small, nan)); \
+ EXPECT_FALSE(less_than(nan, nan)); \
+ }
+
+#define TEST_GREATER_THAN(Name, Type) \
+ TEST(LlvmLibc##Name##ComparisionOperationsTest, GreaterThan) { \
+ using Bits = LIBC_NAMESPACE::fputil::FPBits<Type>; \
+ Type pos_zero = Bits::zero().get_val(); \
+ Type neg_zero = -pos_zero; \
+ Type pos_inf = Bits::inf().get_val(); \
+ Type neg_inf = Bits::inf(Sign::NEG).get_val(); \
+ Type nan = NAN; \
+ Type pos_small = Type(0.1); \
+ Type neg_small = Type(-0.1); \
+ Type pos_large = Type(1000000.0); \
+ Type neg_large = Type(-1000000.0); \
+ \
+ EXPECT_TRUE(greater_than(pos_small, neg_small)); \
+ EXPECT_TRUE(greater_than(pos_large, pos_small)); \
+ EXPECT_TRUE(greater_than(neg_small, neg_large)); \
+ EXPECT_FALSE(greater_than(pos_small, pos_large)); \
+ EXPECT_FALSE(greater_than(neg_small, pos_small)); \
+ \
+ EXPECT_FALSE(greater_than(pos_zero, neg_zero)); \
+ EXPECT_FALSE(greater_than(neg_zero, pos_zero)); \
+ \
+ EXPECT_TRUE(greater_than(pos_inf, neg_inf)); \
+ EXPECT_TRUE(greater_than(pos_inf, pos_small)); \
+ EXPECT_TRUE(greater_than(pos_small, neg_inf)); \
+ EXPECT_FALSE(greater_than(neg_inf, pos_inf)); \
+ \
+ EXPECT_FALSE(greater_than(pos_small, pos_small)); \
+ EXPECT_FALSE(greater_than(pos_inf, pos_inf)); \
+ \
+ EXPECT_FALSE(greater_than(nan, pos_small)); \
+ EXPECT_FALSE(greater_than(pos_small, nan)); \
+ EXPECT_FALSE(greater_than(nan, nan)); \
+ }
+
+#define TEST_LESS_THAN_OR_EQUALS(Name, Type) \
+ TEST(LlvmLibc##Name##ComparisionOperationsTest, LessThanOrEquals) { \
+ using Bits = LIBC_NAMESPACE::fputil::FPBits<Type>; \
+ Type pos_zero = Bits::zero().get_val(); \
+ Type neg_zero = -pos_zero; \
+ Type pos_inf = Bits::inf().get_val(); \
+ Type neg_inf = Bits::inf(Sign::NEG).get_val(); \
+ Type nan = NAN; \
+ Type pos_small = Type(0.1); \
+ Type neg_small = Type(-0.1); \
+ Type pos_large = Type(1000000.0); \
+ Type neg_large = Type(-1000000.0); \
+ \
+ EXPECT_TRUE(less_than_or_equals(neg_small, pos_small)); \
+ EXPECT_TRUE(less_than_or_equals(pos_small, pos_large)); \
+ EXPECT_TRUE(less_than_or_equals(neg_inf, pos_small)); \
+ \
+ EXPECT_TRUE(less_than_or_equals(pos_small, pos_small)); \
+ EXPECT_TRUE(less_than_or_equals(pos_zero, neg_zero)); \
+ EXPECT_TRUE(less_than_or_equals(pos_inf, pos_inf)); \
+ \
+ EXPECT_FALSE(less_than_or_equals(pos_small, neg_small)); \
+ EXPECT_FALSE(less_than_or_equals(pos_large, pos_small)); \
+ EXPECT_FALSE(less_than_or_equals(pos_inf, pos_small)); \
+ \
+ EXPECT_TRUE(less_than_or_equals(neg_large, pos_small)); \
+ EXPECT_FALSE(less_than_or_equals(pos_large, neg_small)); \
+ \
+ EXPECT_FALSE(less_than_or_equals(nan, pos_small)); \
+ EXPECT_FALSE(less_than_or_equals(pos_small, nan)); \
+ EXPECT_FALSE(l...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/144983
More information about the libc-commits
mailing list