[libc-commits] [libc] e789f8b - [libc][math] Add Generic Comparison Operations for floating point types (#144983)

via libc-commits libc-commits at lists.llvm.org
Tue Jul 22 11:05:33 PDT 2025


Author: Krishna Pandey
Date: 2025-07-22T14:05:28-04:00
New Revision: e789f8bdf3691e8e5c4b8d0c0d90fc46cd015fee

URL: https://github.com/llvm/llvm-project/commit/e789f8bdf3691e8e5c4b8d0c0d90fc46cd015fee
DIFF: https://github.com/llvm/llvm-project/commit/e789f8bdf3691e8e5c4b8d0c0d90fc46cd015fee.diff

LOG: [libc][math] Add Generic Comparison Operations for floating point types (#144983)

The PR implements the following generic comparison operation functions
for floating point types along with unittests:
- `fputil::equals`
- `fputil::less_than`
- `fputil::less_than_or_equals`
- `fputil::greater_than`
- `fputil::greater_than_or_equals`

---------

Signed-off-by: krishna2803 <kpandey81930 at gmail.com>
Signed-off-by: Krishna Pandey <kpandey81930 at gmail.com>
Co-authored-by: OverMighty <its.overmighty at gmail.com>

Added: 
    libc/src/__support/FPUtil/comparison_operations.h
    libc/test/src/__support/FPUtil/comparison_operations_test.cpp

Modified: 
    libc/src/__support/FPUtil/CMakeLists.txt
    libc/test/src/__support/FPUtil/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/libc/src/__support/FPUtil/CMakeLists.txt b/libc/src/__support/FPUtil/CMakeLists.txt
index cc941f23135a6..f157d90abb8aa 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
+    comparison_operations.h
+  DEPENDS
+    .fenv_impl
+    .fp_bits
+    libc.src.__support.CPP.type_traits
+    libc.src.__support.macros.config
+)
+
 add_header_library(
   hypot
   HDRS

diff  --git a/libc/src/__support/FPUtil/comparison_operations.h b/libc/src/__support/FPUtil/comparison_operations.h
new file mode 100644
index 0000000000000..ff62ce085513b
--- /dev/null
+++ b/libc/src/__support/FPUtil/comparison_operations.h
@@ -0,0 +1,114 @@
+//===-- 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"
+#include "FPBits.h"
+#include "src/__support/CPP/type_traits.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace fputil {
+
+// All predicates are hereby implemented as per IEEE Std 754-2019
+// Implements compareQuietEqual predicate
+// 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 returns false
+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;
+
+  return x_bits.uintval() == y_bits.uintval();
+}
+
+// Implements compareSignalingLess predicate
+// Section 5.11 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);
+
+  // Any comparison with NaN returns false
+  if (x_bits.is_nan() || y_bits.is_nan()) {
+    fputil::raise_except_if_required(FE_INVALID);
+    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 floating-point numbers are stored in the format: s | e | m
+  // we can directly compare the 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();
+}
+
+// Implements compareSignalingGreater predicate
+// 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);
+}
+
+// Implements compareSignalingLessEqual predicate
+// 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);
+}
+
+// Implements compareSignalingGreaterEqual predicate
+// 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 039c700a6ceb0..81db4ccae44c6 100644
--- a/libc/test/src/__support/FPUtil/CMakeLists.txt
+++ b/libc/test/src/__support/FPUtil/CMakeLists.txt
@@ -55,3 +55,15 @@ add_fp_unittest(
   DEPENDS
     libc.src.__support.FPUtil.bfloat16
 )
+
+add_fp_unittest(
+  comparison_operations_test
+  SUITE
+    libc-fputil-tests
+  SRCS
+    comparison_operations_test.cpp
+  DEPENDS
+    libc.src.__support.FPUtil.bfloat16
+    libc.src.__support.FPUtil.comparison_operations
+    libc.src.__support.macros.properties.types
+)

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..04a3321fd5dbf
--- /dev/null
+++ b/libc/test/src/__support/FPUtil/comparison_operations_test.cpp
@@ -0,0 +1,350 @@
+//===-- Unittests for comparison operations on floating-point numbers -----===//
+//
+// 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/bfloat16.h"
+#include "src/__support/FPUtil/comparison_operations.h"
+#include "src/__support/macros/properties/types.h"
+#include "test/UnitTest/FEnvSafeTest.h"
+#include "test/UnitTest/FPMatcher.h"
+#include "test/UnitTest/Test.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 BFloat16 = LIBC_NAMESPACE::fputil::BFloat16;
+
+template <typename T>
+class ComparisonOperationsTest : public LIBC_NAMESPACE::testing::FEnvSafeTest {
+  DECLARE_SPECIAL_CONSTANTS(T)
+
+  // TODO: Make these constexpr once quick_get_round is made constexpr.
+  T normal1;
+  T neg_normal1;
+  T normal2;
+  T small;
+  T neg_small;
+  T large;
+  T neg_large;
+
+public:
+  void SetUp() override {
+    with_fenv_preserved([this]() {
+      normal1 = T(3.14);
+      neg_normal1 = T(-3.14);
+      normal2 = T(2.71);
+      small = T(0.1);
+      neg_small = T(-0.1);
+      large = T(10000.0);
+      neg_large = T(-10000.0);
+    });
+  }
+
+  void test_equals() {
+    EXPECT_TRUE(equals(neg_zero, neg_zero));
+    EXPECT_TRUE(equals(zero, neg_zero));
+    EXPECT_TRUE(equals(neg_zero, zero));
+
+    EXPECT_TRUE(equals(inf, inf));
+    EXPECT_TRUE(equals(neg_inf, neg_inf));
+    EXPECT_FALSE(equals(inf, neg_inf));
+    EXPECT_FALSE(equals(neg_inf, inf));
+
+    EXPECT_TRUE(equals(normal1, normal1));
+    EXPECT_TRUE(equals(normal2, normal2));
+    EXPECT_FALSE(equals(normal1, normal2));
+    EXPECT_FALSE(equals(normal1, neg_normal1));
+
+    auto test_qnan = [&](T x, T y) {
+      LIBC_NAMESPACE::fputil::clear_except(FE_ALL_EXCEPT);
+      EXPECT_FALSE(equals(x, y));
+      EXPECT_FP_EXCEPTION(0);
+    };
+
+    test_qnan(aNaN, aNaN);
+    test_qnan(aNaN, neg_aNaN);
+    test_qnan(aNaN, zero);
+    test_qnan(aNaN, inf);
+    test_qnan(aNaN, normal1);
+
+    test_qnan(neg_aNaN, neg_aNaN);
+    test_qnan(neg_aNaN, aNaN);
+    test_qnan(neg_aNaN, zero);
+    test_qnan(neg_aNaN, inf);
+    test_qnan(neg_aNaN, normal1);
+
+    auto test_snan = [&](T x, T y) {
+      LIBC_NAMESPACE::fputil::clear_except(FE_ALL_EXCEPT);
+      EXPECT_FALSE(equals(x, y));
+      EXPECT_FP_EXCEPTION(FE_INVALID);
+    };
+
+    test_snan(sNaN, sNaN);
+    test_snan(sNaN, neg_sNaN);
+    test_snan(sNaN, aNaN);
+    test_snan(sNaN, neg_aNaN);
+    test_snan(sNaN, zero);
+    test_snan(sNaN, neg_zero);
+    test_snan(sNaN, inf);
+    test_snan(sNaN, neg_inf);
+    test_snan(sNaN, normal1);
+
+    test_snan(neg_sNaN, neg_sNaN);
+    test_snan(neg_sNaN, sNaN);
+    test_snan(neg_sNaN, aNaN);
+    test_snan(neg_sNaN, neg_aNaN);
+    test_snan(neg_sNaN, zero);
+    test_snan(neg_sNaN, neg_zero);
+    test_snan(neg_sNaN, inf);
+    test_snan(neg_sNaN, neg_inf);
+    test_snan(neg_sNaN, normal1);
+  }
+
+  void test_less_than() {
+    EXPECT_TRUE(less_than(neg_small, small));
+    EXPECT_TRUE(less_than(small, large));
+
+    EXPECT_TRUE(less_than(neg_large, neg_small));
+    EXPECT_FALSE(less_than(large, small));
+    EXPECT_FALSE(less_than(small, neg_small));
+
+    EXPECT_FALSE(less_than(zero, neg_zero));
+    EXPECT_FALSE(less_than(neg_zero, zero));
+    EXPECT_FALSE(less_than(zero, zero));
+
+    EXPECT_TRUE(less_than(neg_small, zero));
+    EXPECT_TRUE(less_than(neg_zero, small));
+    EXPECT_FALSE(less_than(small, zero));
+
+    EXPECT_TRUE(less_than(neg_inf, inf));
+    EXPECT_TRUE(less_than(neg_inf, neg_small));
+    EXPECT_TRUE(less_than(small, inf));
+    EXPECT_FALSE(less_than(inf, small));
+
+    EXPECT_FALSE(less_than(small, small));
+    EXPECT_FALSE(less_than(neg_inf, neg_inf));
+
+    auto test_qnan = [&](T x, T y) {
+      LIBC_NAMESPACE::fputil::clear_except(FE_ALL_EXCEPT);
+      EXPECT_FALSE(less_than(x, y));
+      EXPECT_FP_EXCEPTION(FE_INVALID);
+    };
+
+    test_qnan(aNaN, small);
+    test_qnan(small, aNaN);
+    test_qnan(aNaN, aNaN);
+    test_qnan(neg_aNaN, neg_small);
+    test_qnan(neg_small, neg_aNaN);
+    test_qnan(neg_aNaN, neg_aNaN);
+
+    auto test_snan = [&](T x, T y) {
+      LIBC_NAMESPACE::fputil::clear_except(FE_ALL_EXCEPT);
+      EXPECT_FALSE(less_than(x, y));
+      EXPECT_FP_EXCEPTION(FE_INVALID);
+    };
+
+    test_snan(sNaN, small);
+    test_snan(sNaN, neg_small);
+    test_snan(sNaN, zero);
+    test_snan(sNaN, inf);
+    test_snan(sNaN, aNaN);
+    test_snan(sNaN, sNaN);
+
+    test_snan(neg_sNaN, small);
+    test_snan(neg_sNaN, neg_small);
+    test_snan(neg_sNaN, zero);
+    test_snan(neg_sNaN, inf);
+    test_snan(neg_sNaN, aNaN);
+    test_snan(neg_sNaN, neg_sNaN);
+  }
+
+  void test_greater_than() {
+    EXPECT_TRUE(greater_than(large, neg_small));
+    EXPECT_TRUE(greater_than(neg_small, neg_large));
+
+    EXPECT_FALSE(greater_than(large, large));
+    EXPECT_FALSE(greater_than(neg_small, large));
+
+    EXPECT_FALSE(greater_than(zero, neg_zero));
+    EXPECT_FALSE(greater_than(neg_zero, zero));
+
+    EXPECT_TRUE(greater_than(inf, neg_inf));
+    EXPECT_TRUE(greater_than(inf, large));
+    EXPECT_TRUE(greater_than(large, neg_inf));
+    EXPECT_FALSE(greater_than(neg_inf, inf));
+
+    EXPECT_FALSE(greater_than(large, large));
+    EXPECT_FALSE(greater_than(inf, inf));
+
+    auto test_qnan = [&](T x, T y) {
+      LIBC_NAMESPACE::fputil::clear_except(FE_ALL_EXCEPT);
+      EXPECT_FALSE(greater_than(x, y));
+      EXPECT_FP_EXCEPTION(FE_INVALID);
+    };
+
+    test_qnan(aNaN, large);
+    test_qnan(large, aNaN);
+    test_qnan(aNaN, aNaN);
+    test_qnan(neg_aNaN, neg_small);
+    test_qnan(neg_small, neg_aNaN);
+    test_qnan(neg_aNaN, neg_aNaN);
+
+    auto test_snan = [&](T x, T y) {
+      LIBC_NAMESPACE::fputil::clear_except(FE_ALL_EXCEPT);
+      EXPECT_FALSE(greater_than(x, y));
+      EXPECT_FP_EXCEPTION(FE_INVALID);
+    };
+
+    test_snan(sNaN, large);
+    test_snan(sNaN, neg_small);
+    test_snan(sNaN, zero);
+    test_snan(sNaN, inf);
+    test_snan(sNaN, aNaN);
+    test_snan(sNaN, sNaN);
+
+    test_snan(neg_sNaN, large);
+    test_snan(neg_sNaN, neg_small);
+    test_snan(neg_sNaN, zero);
+    test_snan(neg_sNaN, inf);
+    test_snan(neg_sNaN, aNaN);
+    test_snan(neg_sNaN, neg_sNaN);
+  }
+
+  void test_less_than_or_equals() {
+    EXPECT_TRUE(less_than_or_equals(neg_small, small));
+    EXPECT_TRUE(less_than_or_equals(small, large));
+    EXPECT_TRUE(less_than_or_equals(neg_inf, small));
+
+    EXPECT_TRUE(less_than_or_equals(small, small));
+    EXPECT_TRUE(less_than_or_equals(zero, neg_zero));
+    EXPECT_TRUE(less_than_or_equals(inf, inf));
+
+    EXPECT_FALSE(less_than_or_equals(small, neg_small));
+    EXPECT_FALSE(less_than_or_equals(large, small));
+    EXPECT_FALSE(less_than_or_equals(inf, small));
+
+    EXPECT_TRUE(less_than_or_equals(neg_large, small));
+    EXPECT_FALSE(less_than_or_equals(large, neg_small));
+
+    auto test_qnan = [&](T x, T y) {
+      LIBC_NAMESPACE::fputil::clear_except(FE_ALL_EXCEPT);
+      EXPECT_FALSE(less_than_or_equals(x, y));
+      EXPECT_FP_EXCEPTION(FE_INVALID);
+    };
+
+    test_qnan(aNaN, small);
+    test_qnan(small, aNaN);
+    test_qnan(aNaN, aNaN);
+    test_qnan(neg_aNaN, neg_small);
+    test_qnan(neg_small, neg_aNaN);
+    test_qnan(neg_aNaN, neg_aNaN);
+
+    auto test_snan = [&](T x, T y) {
+      LIBC_NAMESPACE::fputil::clear_except(FE_ALL_EXCEPT);
+      EXPECT_FALSE(less_than_or_equals(x, y));
+      EXPECT_FP_EXCEPTION(FE_INVALID);
+    };
+
+    test_snan(sNaN, small);
+    test_snan(sNaN, neg_small);
+    test_snan(sNaN, zero);
+    test_snan(sNaN, inf);
+    test_snan(sNaN, aNaN);
+    test_snan(sNaN, sNaN);
+
+    test_snan(neg_sNaN, small);
+    test_snan(neg_sNaN, neg_small);
+    test_snan(neg_sNaN, zero);
+    test_snan(neg_sNaN, inf);
+    test_snan(neg_sNaN, aNaN);
+    test_snan(neg_sNaN, neg_sNaN);
+  }
+
+  void test_greater_than_or_equals() {
+    EXPECT_TRUE(greater_than_or_equals(small, neg_small));
+    EXPECT_TRUE(greater_than_or_equals(large, small));
+    EXPECT_TRUE(greater_than_or_equals(inf, small));
+
+    EXPECT_TRUE(greater_than_or_equals(small, small));
+    EXPECT_TRUE(greater_than_or_equals(zero, neg_zero));
+    EXPECT_TRUE(greater_than_or_equals(neg_inf, neg_inf));
+
+    EXPECT_FALSE(greater_than_or_equals(neg_small, small));
+    EXPECT_FALSE(greater_than_or_equals(small, large));
+    EXPECT_FALSE(greater_than_or_equals(neg_inf, small));
+
+    EXPECT_TRUE(greater_than_or_equals(large, neg_small));
+    EXPECT_FALSE(greater_than_or_equals(neg_large, small));
+
+    auto test_qnan = [&](T x, T y) {
+      LIBC_NAMESPACE::fputil::clear_except(FE_ALL_EXCEPT);
+      EXPECT_FALSE(greater_than_or_equals(x, y));
+      EXPECT_FP_EXCEPTION(FE_INVALID);
+    };
+
+    test_qnan(aNaN, small);
+    test_qnan(small, aNaN);
+    test_qnan(aNaN, aNaN);
+    test_qnan(neg_aNaN, neg_small);
+    test_qnan(neg_small, neg_aNaN);
+    test_qnan(neg_aNaN, neg_aNaN);
+
+    auto test_snan = [&](T x, T y) {
+      LIBC_NAMESPACE::fputil::clear_except(FE_ALL_EXCEPT);
+      EXPECT_FALSE(greater_than_or_equals(x, y));
+      EXPECT_FP_EXCEPTION(FE_INVALID);
+    };
+
+    test_snan(sNaN, small);
+    test_snan(sNaN, neg_small);
+    test_snan(sNaN, zero);
+    test_snan(sNaN, inf);
+    test_snan(sNaN, aNaN);
+    test_snan(sNaN, sNaN);
+
+    test_snan(neg_sNaN, small);
+    test_snan(neg_sNaN, neg_small);
+    test_snan(neg_sNaN, zero);
+    test_snan(neg_sNaN, inf);
+    test_snan(neg_sNaN, aNaN);
+    test_snan(neg_sNaN, neg_sNaN);
+  }
+};
+
+#define TEST_COMPARISON_OPS(Name, Type)                                        \
+  using LlvmLibc##Name##ComparisonOperationsTest =                             \
+      ComparisonOperationsTest<Type>;                                          \
+  TEST_F(LlvmLibc##Name##ComparisonOperationsTest, Equals) { test_equals(); }  \
+  TEST_F(LlvmLibc##Name##ComparisonOperationsTest, LessThan) {                 \
+    test_less_than();                                                          \
+  }                                                                            \
+  TEST_F(LlvmLibc##Name##ComparisonOperationsTest, GreaterThan) {              \
+    test_greater_than();                                                       \
+  }                                                                            \
+  TEST_F(LlvmLibc##Name##ComparisonOperationsTest, LessThanOrEquals) {         \
+    test_less_than_or_equals();                                                \
+  }                                                                            \
+  TEST_F(LlvmLibc##Name##ComparisonOperationsTest, GreaterThanOrEquals) {      \
+    test_greater_than_or_equals();                                             \
+  }
+
+TEST_COMPARISON_OPS(Float, float)
+TEST_COMPARISON_OPS(Double, double)
+TEST_COMPARISON_OPS(LongDouble, long double)
+
+#ifdef LIBC_TYPES_HAS_FLOAT16
+TEST_COMPARISON_OPS(Float16, float16)
+#endif // LIBC_TYPES_HAS_FLOAT16
+
+#ifdef LIBC_TYPES_HAS_FLOAT128
+TEST_COMPARISON_OPS(Float128, float128)
+#endif // LIBC_TYPES_HAS_FLOAT128
+
+TEST_COMPARISON_OPS(BFloat16, BFloat16)


        


More information about the libc-commits mailing list