//===-- Implementation header for atan --------------------------*- 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_MATH_ATAN_H
#define LLVM_LIBC_SRC___SUPPORT_MATH_ATAN_H

#include "atan_utils.h"
#include "src/__support/FPUtil/FEnvImpl.h"
#include "src/__support/FPUtil/FPBits.h"
#include "src/__support/FPUtil/double_double.h"
#include "src/__support/FPUtil/multiply_add.h"
#include "src/__support/FPUtil/nearest_integer.h"
#include "src/__support/macros/config.h"
#include "src/__support/macros/optimization.h" // LIBC_UNLIKELY

namespace LIBC_NAMESPACE_DECL {

namespace math {

// To compute atan(x), we divided it into the following cases:
// * |x| < 2^-26:
//      Since |x| > atan(|x|) > |x| - |x|^3/3, and |x|^3/3 < ulp(x)/2, we simply
//      return atan(x) = x - sign(x) * epsilon.
// * 2^-26 <= |x| < 1:
//      We perform range reduction mod 2^-6 = 1/64 as follow:
//      Let k = 2^(-6) * round(|x| * 2^6), then
//        atan(x) = sign(x) * atan(|x|)
//                = sign(x) * (atan(k) + atan((|x| - k) / (1 + |x|*k)).
//      We store atan(k) in a look up table, and perform intermediate steps in
//      double-double.
// * 1 < |x| < 2^53:
//      First we perform the transformation y = 1/|x|:
//        atan(x) = sign(x) * (pi/2 - atan(1/|x|))
//                = sign(x) * (pi/2 - atan(y)).
//      Then we compute atan(y) using range reduction mod 2^-6 = 1/64 as the
//      previous case:
//      Let k = 2^(-6) * round(y * 2^6), then
//        atan(y) = atan(k) + atan((y - k) / (1 + y*k))
//                = atan(k) + atan((1/|x| - k) / (1 + k/|x|)
//                = atan(k) + atan((1 - k*|x|) / (|x| + k)).
// * |x| >= 2^53:
//      Using the reciprocal transformation:
//        atan(x) = sign(x) * (pi/2 - atan(1/|x|)).
//      We have that:
//        atan(1/|x|) <= 1/|x| <= 2^-53,
//      which is smaller than ulp(pi/2) / 2.
//      So we can return:
//        atan(x) = sign(x) * (pi/2 - epsilon)

LIBC_INLINE double atan(double x) {

  using namespace atan_internal;
  using FPBits = fputil::FPBits<double>;

  constexpr double IS_NEG[2] = {1.0, -1.0};
  constexpr DoubleDouble PI_OVER_2 = {0x1.1a62633145c07p-54,
                                      0x1.921fb54442d18p0};
  constexpr DoubleDouble MPI_OVER_2 = {-0x1.1a62633145c07p-54,
                                       -0x1.921fb54442d18p0};

  FPBits xbits(x);
  bool x_sign = xbits.is_neg();
  xbits = xbits.abs();
  uint64_t x_abs = xbits.uintval();
  int x_exp =
      static_cast<int>(x_abs >> FPBits::FRACTION_LEN) - FPBits::EXP_BIAS;

  // |x| < 1.
  if (x_exp < 0) {
    if (LIBC_UNLIKELY(x_exp < -26)) {
#ifdef LIBC_MATH_HAS_SKIP_ACCURATE_PASS
      return x;
#else
      if (x == 0.0)
        return x;
      // |x| < 2^-26
      return fputil::multiply_add(-0x1.0p-54, x, x);
#endif // LIBC_MATH_HAS_SKIP_ACCURATE_PASS
    }

    double x_d = xbits.get_val();
    // k = 2^-6 * round(2^6 * |x|)
    double k = fputil::nearest_integer(0x1.0p6 * x_d);
    unsigned idx = static_cast<unsigned>(k);
    k *= 0x1.0p-6;

    // numerator = |x| - k
    DoubleDouble num, den;
    num.lo = 0.0;
    num.hi = x_d - k;

    // denominator = 1 - k * |x|
    den.hi = fputil::multiply_add(x_d, k, 1.0);
    DoubleDouble prod = fputil::exact_mult(x_d, k);
    // Using Dekker's 2SUM algorithm to compute the lower part.
    den.lo = ((1.0 - den.hi) + prod.hi) + prod.lo;

    // x_r = (|x| - k) / (1 + k * |x|)
    DoubleDouble x_r = fputil::div(num, den);

    // Approximating atan(x_r) using Taylor polynomial.
    DoubleDouble p = atan_eval(x_r);

    // atan(x) = sign(x) * (atan(k) + atan(x_r))
    //         = sign(x) * (atan(k) + atan( (|x| - k) / (1 + k * |x|) ))
#ifdef LIBC_MATH_HAS_SKIP_ACCURATE_PASS
    return IS_NEG[x_sign] * (ATAN_I[idx].hi + (p.hi + (p.lo + ATAN_I[idx].lo)));
#else

    DoubleDouble c0 = fputil::exact_add(ATAN_I[idx].hi, p.hi);
    double c1 = c0.lo + (ATAN_I[idx].lo + p.lo);
    double r = IS_NEG[x_sign] * (c0.hi + c1);

    return r;
#endif // LIBC_MATH_HAS_SKIP_ACCURATE_PASS
  }

  // |x| >= 2^53 or x is NaN.
  if (LIBC_UNLIKELY(x_exp >= 53)) {
    // x is nan
    if (xbits.is_nan()) {
      if (xbits.is_signaling_nan()) {
        fputil::raise_except_if_required(FE_INVALID);
        return FPBits::quiet_nan().get_val();
      }
      return x;
    }
    // |x| >= 2^53
    // atan(x) ~ sign(x) * pi/2.
    if (x_exp >= 53)
#ifdef LIBC_MATH_HAS_SKIP_ACCURATE_PASS
      return IS_NEG[x_sign] * PI_OVER_2.hi;
#else
      return fputil::multiply_add(IS_NEG[x_sign], PI_OVER_2.hi,
                                  IS_NEG[x_sign] * PI_OVER_2.lo);
#endif // LIBC_MATH_HAS_SKIP_ACCURATE_PASS
  }

  double x_d = xbits.get_val();
  double y = 1.0 / x_d;

  // k = 2^-6 * round(2^6 / |x|)
  double k = fputil::nearest_integer(0x1.0p6 * y);
  unsigned idx = static_cast<unsigned>(k);
  k *= 0x1.0p-6;

  // denominator = |x| + k
  DoubleDouble den = fputil::exact_add(x_d, k);
  // numerator = 1 - k * |x|
  DoubleDouble num;
  num.hi = fputil::multiply_add(-x_d, k, 1.0);
  DoubleDouble prod = fputil::exact_mult(x_d, k);
  // Using Dekker's 2SUM algorithm to compute the lower part.
  num.lo = ((1.0 - num.hi) - prod.hi) - prod.lo;

  // x_r = (1/|x| - k) / (1 - k/|x|)
  //     = (1 - k * |x|) / (|x| - k)
  DoubleDouble x_r = fputil::div(num, den);

  // Approximating atan(x_r) using Taylor polynomial.
  DoubleDouble p = atan_eval(x_r);

  // atan(x) = sign(x) * (pi/2 - atan(1/|x|))
  //         = sign(x) * (pi/2 - atan(k) - atan(x_r))
  //         = (-sign(x)) * (-pi/2 + atan(k) + atan((1 - k*|x|)/(|x| - k)))
#ifdef LIBC_MATH_HAS_SKIP_ACCURATE_PASS
  double lo_part = p.lo + ATAN_I[idx].lo + MPI_OVER_2.lo;
  return IS_NEG[!x_sign] * (MPI_OVER_2.hi + ATAN_I[idx].hi + (p.hi + lo_part));
#else
  DoubleDouble c0 = fputil::exact_add(MPI_OVER_2.hi, ATAN_I[idx].hi);
  DoubleDouble c1 = fputil::exact_add(c0.hi, p.hi);
  double c2 = c1.lo + (c0.lo + p.lo) + (ATAN_I[idx].lo + MPI_OVER_2.lo);

  double r = IS_NEG[!x_sign] * (c1.hi + c2);

  return r;
#endif
}

} // namespace math

} // namespace LIBC_NAMESPACE_DECL

#endif // LLVM_LIBC_SRC___SUPPORT_MATH_ATAN_H
