//===-- 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
