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 +#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 +LIBC_INLINE cpp::enable_if_t, bool> equals(T x, + T y) { + using FPBits = FPBits; + 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 +LIBC_INLINE cpp::enable_if_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 +LIBC_INLINE cpp::enable_if_t, bool> less_than(T x, + T y) { + using FPBits = FPBits; + 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 +LIBC_INLINE cpp::enable_if_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 +LIBC_INLINE cpp::enable_if_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 +LIBC_INLINE cpp::enable_if_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..47494ecf70fe4 --- /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 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 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 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 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 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(less_than_or_equals(nan, nan)); \ + } + +#define TEST_GREATER_THAN_OR_EQUALS(Name, Type) \ + TEST(LlvmLibc##Name##ComparisionOperationsTest, GreaterThanOrEquals) { \ + using Bits = LIBC_NAMESPACE::fputil::FPBits; \ + 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_or_equals(pos_small, neg_small)); \ + EXPECT_TRUE(greater_than_or_equals(pos_large, pos_small)); \ + EXPECT_TRUE(greater_than_or_equals(pos_inf, pos_small)); \ + \ + EXPECT_TRUE(greater_than_or_equals(pos_small, pos_small)); \ + EXPECT_TRUE(greater_than_or_equals(pos_zero, neg_zero)); \ + EXPECT_TRUE(greater_than_or_equals(neg_inf, neg_inf)); \ + \ + EXPECT_FALSE(greater_than_or_equals(neg_small, pos_small)); \ + EXPECT_FALSE(greater_than_or_equals(pos_small, pos_large)); \ + EXPECT_FALSE(greater_than_or_equals(neg_inf, pos_small)); \ + \ + EXPECT_TRUE(greater_than_or_equals(pos_large, neg_small)); \ + EXPECT_FALSE(greater_than_or_equals(neg_large, pos_small)); \ + \ + EXPECT_FALSE(greater_than_or_equals(nan, pos_small)); \ + EXPECT_FALSE(greater_than_or_equals(pos_small, nan)); \ + EXPECT_FALSE(greater_than_or_equals(nan, nan)); \ + } + +#define TEST_COMPARISON_OPS(Name, Type) \ + TEST_EQUALS(Name, Type) \ + TEST_NOT_EQUALS(Name, Type) \ + TEST_LESS_THAN(Name, Type) \ + TEST_GREATER_THAN(Name, Type) \ + TEST_LESS_THAN_OR_EQUALS(Name, Type) \ + TEST_GREATER_THAN_OR_EQUALS(Name, Type) + +TEST_COMPARISON_OPS(Float, float) +TEST_COMPARISON_OPS(Double, double) +// FIXME: error: expected '(' for function-style cast or type construction +// TEST_COMPARISON_OPS(LongDouble, (long double)) +#ifdef LIBC_TYPES_HAS_FLOAT16 +TEST_COMPARISON_OPS(Float16, float16) +#endif // LIBC_TYPES_HAS_FLOAT16 + +// TODO: add other types if this is correct?