//===-- Implementation header for powf using less memory --------*- 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_POWF_SMALL_TABLES_H
#define LLVM_LIBC_SRC___SUPPORT_MATH_POWF_SMALL_TABLES_H

#include "src/__support/CPP/bit.h"
#include "src/__support/FPUtil/FPBits.h"
#include "src/__support/FPUtil/PolyEval.h"
#include "src/__support/FPUtil/multiply_add.h"
#include "src/__support/FPUtil/nearest_integer.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
#include "src/__support/macros/optimization.h"

namespace LIBC_NAMESPACE_DECL {

namespace math {

namespace powf_internal {

LIBC_INLINE LIBC_CONSTEXPR float powf_small_tables(float x, int ex,
                                                   uint64_t sign, float y) {
  using FloatBits = fputil::FPBits<float>;
  using DoubleBits = fputil::FPBits<double>;

  constexpr double ONE_OVER_SQRT2 = 0x1.6a09e667f3bcdp-1;

  // x^y = 2^( y * log2(x) )
  //     = 2^( y * ( e_x + log2(m_x) ) )
  // First we compute log2(x) = e_x + log2(m_x)
  uint32_t x_u = FloatBits(x).uintval();

  double yd = static_cast<double>(y);

  // Extract exponent field of x.
  ex += (x_u >> FloatBits::FRACTION_LEN);
  double e_x = static_cast<double>(ex);

  // Add the hidden bit to the mantissa.
  // 1 <= m_x < 2
  uint32_t x_mant = (x_u & FloatBits::FRACTION_MASK);
  double m_x = static_cast<double>(cpp::bit_cast<float>(x_mant | 0x3f800000));
  // Reduce to 1 <= mx <= sqrt(2).
  if (x_mant > 0x0045'04f3) {
    e_x += 0.5;
    m_x *= ONE_OVER_SQRT2;
  }
  // 0 <= dx <= sqrt(2) - 1.
  double dx = m_x - 1.0;

  // Degree-13 polynomial approximation:
  //   dx * P(dx) ~ log2(1 + dx)
  // Generated by Sollya with:
  // > P = fpminimax(log2(1 + x)/x, 13, [|D...|], [0, sqrt(2) - 1]);
  // > dirtyinfnorm((log2(1 + x) - x*P)/log2(1 + x), [0, sqrt(2) - 1]);
  //   0x1.b2d...p-53
  constexpr double LOG2_COEFFS[] = {
      0x1.71547652b82fdp0,   -0x1.71547652b7a2ap-1, 0x1.ec709dc2edfa6p-2,
      -0x1.71547626a9d98p-2, 0x1.2776bf5f6f40ep-2,  -0x1.ec6fbbf289ce3p-3,
      0x1.a60bf904470a7p-3,  -0x1.70ef61b01fc1ep-3, 0x1.45d3270454507p-3,
      -0x1.1c5fc05b06e8fp-3, 0x1.d0f57944a937fp-4,  -0x1.413e22be24d32p-4,
      0x1.3c84b66491ccp-5,   -0x1.3df9cfe5e602ep-7};

  double dx2 = dx * dx;
  double c0 = fputil::multiply_add(dx, LOG2_COEFFS[1], LOG2_COEFFS[0]);
  double c1 = fputil::multiply_add(dx, LOG2_COEFFS[3], LOG2_COEFFS[2]);
  double c2 = fputil::multiply_add(dx, LOG2_COEFFS[5], LOG2_COEFFS[4]);
  double c3 = fputil::multiply_add(dx, LOG2_COEFFS[7], LOG2_COEFFS[6]);
  double c4 = fputil::multiply_add(dx, LOG2_COEFFS[9], LOG2_COEFFS[8]);
  double c5 = fputil::multiply_add(dx, LOG2_COEFFS[11], LOG2_COEFFS[10]);
  double c6 = fputil::multiply_add(dx, LOG2_COEFFS[13], LOG2_COEFFS[12]);

  double dx4 = dx2 * dx2;
  double d0 = fputil::multiply_add(dx2, c1, c0);
  double d1 = fputil::multiply_add(dx2, c3, c2);
  double d2 = fputil::multiply_add(dx2, c5, c4);

  double p = fputil::polyeval(dx4, d0, d1, d2, c6);
  // u ~ y * log2(x).
  double u = yd * fputil::multiply_add(dx, p, e_x);

  double hi = fputil::nearest_integer(u);
  double lo = u - hi;
  int e_hi = static_cast<int>(hi) + DoubleBits::EXP_BIAS;
  double exp_hi = cpp::bit_cast<double>(
      (static_cast<uint64_t>(e_hi) << DoubleBits::FRACTION_LEN) | sign);
  // Degree-6 polynomial approximation P(lo6) ~ 2^(lo6 / 2^6) = 2^(lo).
  // Generated by Sollya with:
  // > P = fpminimax(2^x, 6, [|1, D...|], [-0.5, 0.5]);
  // > dirtyinfnorm(2^x - P, [-0.5, 0.5]);
  // 0x1.5f7...p-29
  constexpr double EXP2_COEFFS[] = {
      0x1.62e430c7b13a8p-1, 0x1.ebfbdd2f82f6fp-3, 0x1.c6aed4f186f34p-5,
      0x1.3b2c96c9aa336p-7, 0x1.5f4553ff53f9p-10, 0x1.4278e5fa9de78p-13};

  double lo2 = lo * lo;
  double f0 = fputil::multiply_add(lo, EXP2_COEFFS[1], EXP2_COEFFS[0]);
  double f1 = fputil::multiply_add(lo, EXP2_COEFFS[3], EXP2_COEFFS[2]);
  double f2 = fputil::multiply_add(lo, EXP2_COEFFS[5], EXP2_COEFFS[4]);

  double pp = fputil::polyeval(lo2, f0, f1, f2);

  double r = fputil::multiply_add(lo, pp, 1.0);

  double result = r * exp_hi;

  return static_cast<float>(result);
}

} // namespace powf_internal
} // namespace math
} // namespace LIBC_NAMESPACE_DECL

#endif // LLVM_LIBC_SRC___SUPPORT_MATH_POWF_SMALL_TABLES_H
