//===-- Utils which wrap MPFR ---------------------------------------------===//
//
// 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 "MPFRUtils.h"
#include "MPCommon.h"

#include "src/__support/CPP/array.h"
#include "src/__support/CPP/stringstream.h"
#include "src/__support/FPUtil/bfloat16.h"
#include "src/__support/FPUtil/fpbits_str.h"
#include "src/__support/macros/config.h"
#include "src/__support/macros/properties/types.h"

namespace LIBC_NAMESPACE_DECL {
namespace testing {
namespace mpfr {
namespace internal {

template <typename InputType>
cpp::enable_if_t<cpp::is_floating_point_v<InputType>, MPFRNumber>
unary_operation(Operation op, InputType input, unsigned int precision,
                RoundingMode rounding) {
  MPFRNumber mpfrInput(input, precision, rounding);
  switch (op) {
  case Operation::Abs:
    return mpfrInput.abs();
  case Operation::Acos:
    return mpfrInput.acos();
  case Operation::Acosh:
    return mpfrInput.acosh();
  case Operation::Acospi:
    return mpfrInput.acospi();
  case Operation::Asin:
    return mpfrInput.asin();
  case Operation::Asinh:
    return mpfrInput.asinh();
  case Operation::Asinpi:
    return mpfrInput.asinpi();
  case Operation::Atan:
    return mpfrInput.atan();
  case Operation::Atanh:
    return mpfrInput.atanh();
  case Operation::Atanpi:
    return mpfrInput.atanpi();
  case Operation::Cbrt:
    return mpfrInput.cbrt();
  case Operation::Ceil:
    return mpfrInput.ceil();
  case Operation::Cos:
    return mpfrInput.cos();
  case Operation::Cosh:
    return mpfrInput.cosh();
  case Operation::Cospi:
    return mpfrInput.cospi();
  case Operation::Erf:
    return mpfrInput.erf();
  case Operation::Exp:
    return mpfrInput.exp();
  case Operation::Exp2:
    return mpfrInput.exp2();
  case Operation::Exp2m1:
    return mpfrInput.exp2m1();
  case Operation::Exp10:
    return mpfrInput.exp10();
  case Operation::Exp10m1:
    return mpfrInput.exp10m1();
  case Operation::Expm1:
    return mpfrInput.expm1();
  case Operation::Floor:
    return mpfrInput.floor();
  case Operation::Log:
    return mpfrInput.log();
  case Operation::Log2:
    return mpfrInput.log2();
  case Operation::Log10:
    return mpfrInput.log10();
  case Operation::Log1p:
    return mpfrInput.log1p();
  case Operation::Mod2PI:
    return mpfrInput.mod_2pi();
  case Operation::ModPIOver2:
    return mpfrInput.mod_pi_over_2();
  case Operation::ModPIOver4:
    return mpfrInput.mod_pi_over_4();
  case Operation::Round:
    return mpfrInput.round();
  case Operation::RoundEven:
    return mpfrInput.roundeven();
  case Operation::Rsqrt:
    return mpfrInput.rsqrt();
  case Operation::Sin:
    return mpfrInput.sin();
  case Operation::Sinpi:
    return mpfrInput.sinpi();
  case Operation::Sinh:
    return mpfrInput.sinh();
  case Operation::Sqrt:
    return mpfrInput.sqrt();
  case Operation::Tan:
    return mpfrInput.tan();
  case Operation::Tanh:
    return mpfrInput.tanh();
  case Operation::Tanpi:
    return mpfrInput.tanpi();
  case Operation::Trunc:
    return mpfrInput.trunc();
  default:
    __builtin_unreachable();
  }
}

template <typename InputType>
cpp::enable_if_t<cpp::is_floating_point_v<InputType>, MPFRNumber>
unary_operation_two_outputs(Operation op, InputType input, int &output,
                            unsigned int precision, RoundingMode rounding) {
  MPFRNumber mpfrInput(input, precision, rounding);
  switch (op) {
  case Operation::Frexp:
    return mpfrInput.frexp(output);
  default:
    __builtin_unreachable();
  }
}

template <typename InputType>
cpp::enable_if_t<cpp::is_floating_point_v<InputType>, MPFRNumber>
binary_operation_one_output(Operation op, InputType x, InputType y,
                            unsigned int precision, RoundingMode rounding) {
  MPFRNumber inputX(x, precision, rounding);
  MPFRNumber inputY(y, precision, rounding);
  switch (op) {
  case Operation::Add:
    return inputX.add(inputY);
  case Operation::Atan2:
    return inputX.atan2(inputY);
  case Operation::Div:
    return inputX.div(inputY);
  case Operation::Fmod:
    return inputX.fmod(inputY);
  case Operation::Hypot:
    return inputX.hypot(inputY);
  case Operation::Mul:
    return inputX.mul(inputY);
  case Operation::Pow:
    return inputX.pow(inputY);
  case Operation::Sub:
    return inputX.sub(inputY);
  default:
    __builtin_unreachable();
  }
}

template <typename InputType>
cpp::enable_if_t<cpp::is_floating_point_v<InputType>, MPFRNumber>
binary_operation_two_outputs(Operation op, InputType x, InputType y,
                             int &output, unsigned int precision,
                             RoundingMode rounding) {
  MPFRNumber inputX(x, precision, rounding);
  MPFRNumber inputY(y, precision, rounding);
  switch (op) {
  case Operation::RemQuo:
    return inputX.remquo(inputY, output);
  default:
    __builtin_unreachable();
  }
}

template <typename InputType>
cpp::enable_if_t<cpp::is_floating_point_v<InputType>, MPFRNumber>
ternary_operation_one_output(Operation op, InputType x, InputType y,
                             InputType z, unsigned int precision,
                             RoundingMode rounding) {
  // For FMA function, we just need to compare with the mpfr_fma with the same
  // precision as InputType.  Using higher precision as the intermediate results
  // to compare might incorrectly fail due to double-rounding errors.
  MPFRNumber inputX(x, precision, rounding);
  MPFRNumber inputY(y, precision, rounding);
  MPFRNumber inputZ(z, precision, rounding);
  switch (op) {
  case Operation::Fma:
    return inputX.fma(inputY, inputZ);
  default:
    __builtin_unreachable();
  }
}

// Remark: For all the explain_*_error functions, we will use std::stringstream
// to build the complete error messages before sending it to the outstream `OS`
// once at the end.  This will stop the error messages from interleaving when
// the tests are running concurrently.
template <typename InputType, typename OutputType>
void explain_unary_operation_single_output_error(Operation op, InputType input,
                                                 OutputType matchValue,
                                                 double ulp_tolerance,
                                                 RoundingMode rounding) {
  unsigned int precision = get_precision<InputType>(ulp_tolerance);
  MPFRNumber mpfrInput(input, precision);
  MPFRNumber mpfr_result;
  mpfr_result = unary_operation(op, input, precision, rounding);
  MPFRNumber mpfrMatchValue(matchValue);
  cpp::array<char, 1024> msg_buf;
  cpp::StringStream msg(msg_buf);
  msg << "Match value not within tolerance value of MPFR result:\n"
      << "  Input decimal: " << mpfrInput.str() << '\n';
  msg << "     Input bits: " << str(FPBits<InputType>(input)) << '\n';
  msg << '\n' << "  Match decimal: " << mpfrMatchValue.str() << '\n';
  msg << "     Match bits: " << str(FPBits<OutputType>(matchValue)) << '\n';
  msg << '\n' << "    MPFR result: " << mpfr_result.str() << '\n';
  msg << "   MPFR rounded: "
      << str(FPBits<OutputType>(mpfr_result.as<OutputType>())) << '\n';
  msg << '\n';
  msg << "      ULP error: " << mpfr_result.ulp_as_mpfr_number(matchValue).str()
      << '\n';
  if (msg.overflow())
    __builtin_unreachable();
  tlog << msg.str();
}

template void explain_unary_operation_single_output_error(Operation op, float,
                                                          float, double,
                                                          RoundingMode);
template void explain_unary_operation_single_output_error(Operation op, double,
                                                          double, double,
                                                          RoundingMode);
template void explain_unary_operation_single_output_error(Operation op,
                                                          long double,
                                                          long double, double,
                                                          RoundingMode);
template void explain_unary_operation_single_output_error(Operation op, double,
                                                          float, double,
                                                          RoundingMode);
template void explain_unary_operation_single_output_error(Operation op,
                                                          long double, float,
                                                          double, RoundingMode);
template void explain_unary_operation_single_output_error(Operation op,
                                                          long double, double,
                                                          double, RoundingMode);

#ifdef LIBC_TYPES_HAS_FLOAT16
template void explain_unary_operation_single_output_error(Operation op, float16,
                                                          float16, double,
                                                          RoundingMode);
template void explain_unary_operation_single_output_error(Operation op, float,
                                                          float16, double,
                                                          RoundingMode);
template void explain_unary_operation_single_output_error(Operation op, double,
                                                          float16, double,
                                                          RoundingMode);
template void explain_unary_operation_single_output_error(Operation op,
                                                          long double, float16,
                                                          double, RoundingMode);
#ifdef LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE
template void explain_unary_operation_single_output_error(Operation op,
                                                          float128, float16,
                                                          double, RoundingMode);
#endif // LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE
#endif // LIBC_TYPES_HAS_FLOAT16

#ifdef LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE
template void explain_unary_operation_single_output_error(Operation op,
                                                          float128, float128,
                                                          double, RoundingMode);
template void explain_unary_operation_single_output_error(Operation op,
                                                          float128, float,
                                                          double, RoundingMode);
template void explain_unary_operation_single_output_error(Operation op,
                                                          float128, double,
                                                          double, RoundingMode);
template void explain_unary_operation_single_output_error(Operation op,
                                                          float128, long double,
                                                          double, RoundingMode);
#endif // LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE

template void explain_unary_operation_single_output_error(Operation op,
                                                          bfloat16, bfloat16,
                                                          double, RoundingMode);

template <typename T>
void explain_unary_operation_two_outputs_error(
    Operation op, T input, const BinaryOutput<T> &libc_result,
    double ulp_tolerance, RoundingMode rounding) {
  unsigned int precision = get_precision<T>(ulp_tolerance);
  MPFRNumber mpfrInput(input, precision);
  int mpfrIntResult;
  MPFRNumber mpfr_result = unary_operation_two_outputs(op, input, mpfrIntResult,
                                                       precision, rounding);

  if (mpfrIntResult != libc_result.i) {
    tlog << "MPFR integral result: " << mpfrIntResult << '\n'
         << "Libc integral result: " << libc_result.i << '\n';
  } else {
    tlog << "Integral result from libc matches integral result from MPFR.\n";
  }

  MPFRNumber mpfrMatchValue(libc_result.f);
  tlog
      << "Libc floating point result is not within tolerance value of the MPFR "
      << "result.\n\n";

  tlog << "            Input decimal: " << mpfrInput.str() << "\n\n";

  tlog << "Libc floating point value: " << mpfrMatchValue.str() << '\n';
  tlog << " Libc floating point bits: " << str(FPBits<T>(libc_result.f))
       << '\n';
  tlog << "\n\n";

  tlog << "              MPFR result: " << mpfr_result.str() << '\n';
  tlog << "             MPFR rounded: " << str(FPBits<T>(mpfr_result.as<T>()))
       << '\n';
  tlog << '\n'
       << "                ULP error: "
       << mpfr_result.ulp_as_mpfr_number(libc_result.f).str() << '\n';
}

template void explain_unary_operation_two_outputs_error<float>(
    Operation, float, const BinaryOutput<float> &, double, RoundingMode);
template void explain_unary_operation_two_outputs_error<double>(
    Operation, double, const BinaryOutput<double> &, double, RoundingMode);
template void explain_unary_operation_two_outputs_error<long double>(
    Operation, long double, const BinaryOutput<long double> &, double,
    RoundingMode);

template <typename T>
void explain_binary_operation_two_outputs_error(
    Operation op, const BinaryInput<T> &input,
    const BinaryOutput<T> &libc_result, double ulp_tolerance,
    RoundingMode rounding) {
  unsigned int precision = get_precision<T>(ulp_tolerance);
  MPFRNumber mpfrX(input.x, precision);
  MPFRNumber mpfrY(input.y, precision);
  int mpfrIntResult;
  MPFRNumber mpfr_result = binary_operation_two_outputs(
      op, input.x, input.y, mpfrIntResult, precision, rounding);
  MPFRNumber mpfrMatchValue(libc_result.f);

  tlog << "Input decimal: x: " << mpfrX.str() << " y: " << mpfrY.str() << '\n'
       << "MPFR integral result: " << mpfrIntResult << '\n'
       << "Libc integral result: " << libc_result.i << '\n'
       << "Libc floating point result: " << mpfrMatchValue.str() << '\n'
       << "               MPFR result: " << mpfr_result.str() << '\n';
  tlog << "Libc floating point result bits: " << str(FPBits<T>(libc_result.f))
       << '\n';
  tlog << "              MPFR rounded bits: "
       << str(FPBits<T>(mpfr_result.as<T>())) << '\n';
  tlog << "ULP error: " << mpfr_result.ulp_as_mpfr_number(libc_result.f).str()
       << '\n';
}

template void explain_binary_operation_two_outputs_error<float>(
    Operation, const BinaryInput<float> &, const BinaryOutput<float> &, double,
    RoundingMode);
template void explain_binary_operation_two_outputs_error<double>(
    Operation, const BinaryInput<double> &, const BinaryOutput<double> &,
    double, RoundingMode);
template void explain_binary_operation_two_outputs_error<long double>(
    Operation, const BinaryInput<long double> &,
    const BinaryOutput<long double> &, double, RoundingMode);

template <typename InputType, typename OutputType>
void explain_binary_operation_one_output_error(
    Operation op, const BinaryInput<InputType> &input, OutputType libc_result,
    double ulp_tolerance, RoundingMode rounding) {
  unsigned int precision = get_precision<InputType>(ulp_tolerance);
  MPFRNumber mpfrX(input.x, precision);
  MPFRNumber mpfrY(input.y, precision);
  FPBits<InputType> xbits(input.x);
  FPBits<InputType> ybits(input.y);
  MPFRNumber mpfr_result =
      binary_operation_one_output(op, input.x, input.y, precision, rounding);
  MPFRNumber mpfrMatchValue(libc_result);

  tlog << "Input decimal: x: " << mpfrX.str() << " y: " << mpfrY.str() << '\n';
  tlog << "First input bits: " << str(FPBits<InputType>(input.x)) << '\n';
  tlog << "Second input bits: " << str(FPBits<InputType>(input.y)) << '\n';

  tlog << "Libc result: " << mpfrMatchValue.str() << '\n'
       << "MPFR result: " << mpfr_result.str() << '\n';
  tlog << "Libc floating point result bits: "
       << str(FPBits<OutputType>(libc_result)) << '\n';
  tlog << "              MPFR rounded bits: "
       << str(FPBits<OutputType>(mpfr_result.as<OutputType>())) << '\n';
  tlog << "ULP error: " << mpfr_result.ulp_as_mpfr_number(libc_result).str()
       << '\n';
}

template void
explain_binary_operation_one_output_error(Operation, const BinaryInput<float> &,
                                          float, double, RoundingMode);
template void explain_binary_operation_one_output_error(
    Operation, const BinaryInput<double> &, float, double, RoundingMode);
template void explain_binary_operation_one_output_error(
    Operation, const BinaryInput<double> &, double, double, RoundingMode);
template void explain_binary_operation_one_output_error(
    Operation, const BinaryInput<long double> &, float, double, RoundingMode);
template void explain_binary_operation_one_output_error(
    Operation, const BinaryInput<long double> &, double, double, RoundingMode);
template void
explain_binary_operation_one_output_error(Operation,
                                          const BinaryInput<long double> &,
                                          long double, double, RoundingMode);
#ifdef LIBC_TYPES_HAS_FLOAT16
template void explain_binary_operation_one_output_error(
    Operation, const BinaryInput<float16> &, float16, double, RoundingMode);
template void
explain_binary_operation_one_output_error(Operation, const BinaryInput<float> &,
                                          float16, double, RoundingMode);
template void explain_binary_operation_one_output_error(
    Operation, const BinaryInput<double> &, float16, double, RoundingMode);
template void explain_binary_operation_one_output_error(
    Operation, const BinaryInput<long double> &, float16, double, RoundingMode);
#endif
#if defined(LIBC_TYPES_HAS_FLOAT128) &&                                        \
    defined(LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE)
template void explain_binary_operation_one_output_error(
    Operation, const BinaryInput<float128> &, float128, double, RoundingMode);
#endif
template void explain_binary_operation_one_output_error(
    Operation, const BinaryInput<bfloat16> &, bfloat16, double, RoundingMode);
template void
explain_binary_operation_one_output_error(Operation, const BinaryInput<float> &,
                                          bfloat16, double, RoundingMode);
template void explain_binary_operation_one_output_error(
    Operation, const BinaryInput<double> &, bfloat16, double, RoundingMode);
template void
explain_binary_operation_one_output_error(Operation,
                                          const BinaryInput<long double> &,
                                          bfloat16, double, RoundingMode);
#if defined(LIBC_TYPES_HAS_FLOAT128) &&                                        \
    defined(LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE)
template void explain_binary_operation_one_output_error(
    Operation, const BinaryInput<float128> &, bfloat16, double, RoundingMode);
#endif // defined(LIBC_TYPES_HAS_FLOAT128) &&
       // defined(LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE)

template <typename InputType, typename OutputType>
void explain_ternary_operation_one_output_error(
    Operation op, const TernaryInput<InputType> &input, OutputType libc_result,
    double ulp_tolerance, RoundingMode rounding) {
  unsigned int precision = get_precision<InputType>(ulp_tolerance);
  MPFRNumber mpfrX(input.x, precision);
  MPFRNumber mpfrY(input.y, precision);
  MPFRNumber mpfrZ(input.z, precision);
  FPBits<InputType> xbits(input.x);
  FPBits<InputType> ybits(input.y);
  FPBits<InputType> zbits(input.z);
  MPFRNumber mpfr_result = ternary_operation_one_output(
      op, input.x, input.y, input.z, precision, rounding);
  MPFRNumber mpfrMatchValue(libc_result);

  tlog << "Input decimal: x: " << mpfrX.str() << " y: " << mpfrY.str()
       << " z: " << mpfrZ.str() << '\n';
  tlog << " First input bits: " << str(FPBits<InputType>(input.x)) << '\n';
  tlog << "Second input bits: " << str(FPBits<InputType>(input.y)) << '\n';
  tlog << " Third input bits: " << str(FPBits<InputType>(input.z)) << '\n';

  tlog << "Libc result: " << mpfrMatchValue.str() << '\n'
       << "MPFR result: " << mpfr_result.str() << '\n';
  tlog << "Libc floating point result bits: "
       << str(FPBits<OutputType>(libc_result)) << '\n';
  tlog << "              MPFR rounded bits: "
       << str(FPBits<OutputType>(mpfr_result.as<OutputType>())) << '\n';
  tlog << "ULP error: " << mpfr_result.ulp_as_mpfr_number(libc_result).str()
       << '\n';
}

template void explain_ternary_operation_one_output_error(
    Operation, const TernaryInput<float> &, float, double, RoundingMode);
template void explain_ternary_operation_one_output_error(
    Operation, const TernaryInput<double> &, float, double, RoundingMode);
template void explain_ternary_operation_one_output_error(
    Operation, const TernaryInput<double> &, double, double, RoundingMode);
template void explain_ternary_operation_one_output_error(
    Operation, const TernaryInput<long double> &, float, double, RoundingMode);
template void explain_ternary_operation_one_output_error(
    Operation, const TernaryInput<long double> &, double, double, RoundingMode);
template void
explain_ternary_operation_one_output_error(Operation,
                                           const TernaryInput<long double> &,
                                           long double, double, RoundingMode);

#ifdef LIBC_TYPES_HAS_FLOAT16
template void explain_ternary_operation_one_output_error(
    Operation, const TernaryInput<float16> &, float16, double, RoundingMode);
template void explain_ternary_operation_one_output_error(
    Operation, const TernaryInput<float> &, float16, double, RoundingMode);
template void explain_ternary_operation_one_output_error(
    Operation, const TernaryInput<double> &, float16, double, RoundingMode);
template void
explain_ternary_operation_one_output_error(Operation,
                                           const TernaryInput<long double> &,
                                           float16, double, RoundingMode);
#endif

template void explain_ternary_operation_one_output_error(
    Operation, const TernaryInput<float> &, bfloat16, double, RoundingMode);
template void explain_ternary_operation_one_output_error(
    Operation, const TernaryInput<double> &, bfloat16, double, RoundingMode);
template void
explain_ternary_operation_one_output_error(Operation,
                                           const TernaryInput<long double> &,
                                           bfloat16, double, RoundingMode);
#if defined(LIBC_TYPES_HAS_FLOAT128) &&                                        \
    defined(LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE)
template void explain_ternary_operation_one_output_error(
    Operation, const TernaryInput<float128> &, bfloat16, double, RoundingMode);
#endif // defined(LIBC_TYPES_HAS_FLOAT128) &&
       // defined(LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE)

template <typename InputType, typename OutputType>
bool compare_unary_operation_single_output(Operation op, InputType input,
                                           OutputType libc_result,
                                           double ulp_tolerance,
                                           RoundingMode rounding) {
  unsigned int precision = get_precision<InputType>(ulp_tolerance);
  MPFRNumber mpfr_result;
  mpfr_result = unary_operation(op, input, precision, rounding);
  double ulp = mpfr_result.ulp(libc_result);
  return (ulp <= ulp_tolerance);
}

template bool compare_unary_operation_single_output(Operation, float, float,
                                                    double, RoundingMode);
template bool compare_unary_operation_single_output(Operation, double, double,
                                                    double, RoundingMode);
template bool compare_unary_operation_single_output(Operation, long double,
                                                    long double, double,
                                                    RoundingMode);
template bool compare_unary_operation_single_output(Operation, double, float,
                                                    double, RoundingMode);
template bool compare_unary_operation_single_output(Operation, long double,
                                                    float, double,
                                                    RoundingMode);
template bool compare_unary_operation_single_output(Operation, long double,
                                                    double, double,
                                                    RoundingMode);
#ifdef LIBC_TYPES_HAS_FLOAT16
template bool compare_unary_operation_single_output(Operation, float16, float16,
                                                    double, RoundingMode);
template bool compare_unary_operation_single_output(Operation, float, float16,
                                                    double, RoundingMode);
template bool compare_unary_operation_single_output(Operation, double, float16,
                                                    double, RoundingMode);
template bool compare_unary_operation_single_output(Operation, long double,
                                                    float16, double,
                                                    RoundingMode);
#ifdef LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE
template bool compare_unary_operation_single_output(Operation, float128,
                                                    float16, double,
                                                    RoundingMode);
#endif // LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE
#endif // LIBC_TYPES_HAS_FLOAT16

#ifdef LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE
template bool compare_unary_operation_single_output(Operation, float128,
                                                    float128, double,
                                                    RoundingMode);
template bool compare_unary_operation_single_output(Operation, float128, float,
                                                    double, RoundingMode);
template bool compare_unary_operation_single_output(Operation, float128, double,
                                                    double, RoundingMode);
template bool compare_unary_operation_single_output(Operation, float128,
                                                    long double, double,
                                                    RoundingMode);
#endif // LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE
template bool compare_unary_operation_single_output(Operation, bfloat16,
                                                    bfloat16, double,
                                                    RoundingMode);

template <typename T>
bool compare_unary_operation_two_outputs(Operation op, T input,
                                         const BinaryOutput<T> &libc_result,
                                         double ulp_tolerance,
                                         RoundingMode rounding) {
  int mpfrIntResult;
  unsigned int precision = get_precision<T>(ulp_tolerance);
  MPFRNumber mpfr_result = unary_operation_two_outputs(op, input, mpfrIntResult,
                                                       precision, rounding);
  double ulp = mpfr_result.ulp(libc_result.f);

  if (mpfrIntResult != libc_result.i)
    return false;

  return (ulp <= ulp_tolerance);
}

template bool compare_unary_operation_two_outputs<float>(
    Operation, float, const BinaryOutput<float> &, double, RoundingMode);
template bool compare_unary_operation_two_outputs<double>(
    Operation, double, const BinaryOutput<double> &, double, RoundingMode);
template bool compare_unary_operation_two_outputs<long double>(
    Operation, long double, const BinaryOutput<long double> &, double,
    RoundingMode);

template <typename T>
bool compare_binary_operation_two_outputs(Operation op,
                                          const BinaryInput<T> &input,
                                          const BinaryOutput<T> &libc_result,
                                          double ulp_tolerance,
                                          RoundingMode rounding) {
  int mpfrIntResult;
  unsigned int precision = get_precision<T>(ulp_tolerance);
  MPFRNumber mpfr_result = binary_operation_two_outputs(
      op, input.x, input.y, mpfrIntResult, precision, rounding);
  double ulp = mpfr_result.ulp(libc_result.f);

  if (mpfrIntResult != libc_result.i) {
    if (op == Operation::RemQuo) {
      if ((0x7 & mpfrIntResult) != (0x7 & libc_result.i))
        return false;
    } else {
      return false;
    }
  }

  return (ulp <= ulp_tolerance);
}

template bool compare_binary_operation_two_outputs<float>(
    Operation, const BinaryInput<float> &, const BinaryOutput<float> &, double,
    RoundingMode);
template bool compare_binary_operation_two_outputs<double>(
    Operation, const BinaryInput<double> &, const BinaryOutput<double> &,
    double, RoundingMode);
template bool compare_binary_operation_two_outputs<long double>(
    Operation, const BinaryInput<long double> &,
    const BinaryOutput<long double> &, double, RoundingMode);

template <typename InputType, typename OutputType>
bool compare_binary_operation_one_output(Operation op,
                                         const BinaryInput<InputType> &input,
                                         OutputType libc_result,
                                         double ulp_tolerance,
                                         RoundingMode rounding) {
  unsigned int precision = get_precision<InputType>(ulp_tolerance);
  MPFRNumber mpfr_result =
      binary_operation_one_output(op, input.x, input.y, precision, rounding);
  double ulp = mpfr_result.ulp(libc_result);

  return (ulp <= ulp_tolerance);
}

template bool compare_binary_operation_one_output(Operation,
                                                  const BinaryInput<float> &,
                                                  float, double, RoundingMode);
template bool compare_binary_operation_one_output(Operation,
                                                  const BinaryInput<double> &,
                                                  double, double, RoundingMode);
template bool
compare_binary_operation_one_output(Operation, const BinaryInput<long double> &,
                                    float, double, RoundingMode);
template bool
compare_binary_operation_one_output(Operation, const BinaryInput<long double> &,
                                    double, double, RoundingMode);
template bool
compare_binary_operation_one_output(Operation, const BinaryInput<long double> &,
                                    long double, double, RoundingMode);

template bool compare_binary_operation_one_output(Operation,
                                                  const BinaryInput<double> &,
                                                  float, double, RoundingMode);
#ifdef LIBC_TYPES_HAS_FLOAT16
template bool compare_binary_operation_one_output(Operation,
                                                  const BinaryInput<float16> &,
                                                  float16, double,
                                                  RoundingMode);
template bool compare_binary_operation_one_output(Operation,
                                                  const BinaryInput<float> &,
                                                  float16, double,
                                                  RoundingMode);
template bool compare_binary_operation_one_output(Operation,
                                                  const BinaryInput<double> &,
                                                  float16, double,
                                                  RoundingMode);
template bool
compare_binary_operation_one_output(Operation, const BinaryInput<long double> &,
                                    float16, double, RoundingMode);
#endif
#if defined(LIBC_TYPES_HAS_FLOAT128) &&                                        \
    defined(LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE)
template bool compare_binary_operation_one_output(Operation,
                                                  const BinaryInput<float128> &,
                                                  float128, double,
                                                  RoundingMode);
#endif
template bool compare_binary_operation_one_output(Operation,
                                                  const BinaryInput<bfloat16> &,
                                                  bfloat16, double,
                                                  RoundingMode);

template bool compare_binary_operation_one_output(Operation,
                                                  const BinaryInput<float> &,
                                                  bfloat16, double,
                                                  RoundingMode);
template bool compare_binary_operation_one_output(Operation,
                                                  const BinaryInput<double> &,
                                                  bfloat16, double,
                                                  RoundingMode);
template bool
compare_binary_operation_one_output(Operation, const BinaryInput<long double> &,
                                    bfloat16, double, RoundingMode);
#if defined(LIBC_TYPES_HAS_FLOAT128) &&                                        \
    defined(LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE)
template bool compare_binary_operation_one_output(Operation,
                                                  const BinaryInput<float128> &,
                                                  bfloat16, double,
                                                  RoundingMode);
#endif // defined(LIBC_TYPES_HAS_FLOAT128) &&
       // defined(LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE)
template <typename InputType, typename OutputType>
bool compare_ternary_operation_one_output(Operation op,
                                          const TernaryInput<InputType> &input,
                                          OutputType libc_result,
                                          double ulp_tolerance,
                                          RoundingMode rounding) {
  unsigned int precision = get_precision<InputType>(ulp_tolerance);
  MPFRNumber mpfr_result = ternary_operation_one_output(
      op, input.x, input.y, input.z, precision, rounding);
  double ulp = mpfr_result.ulp(libc_result);

  return (ulp <= ulp_tolerance);
}

template bool compare_ternary_operation_one_output(Operation,
                                                   const TernaryInput<float> &,
                                                   float, double, RoundingMode);
template bool compare_ternary_operation_one_output(Operation,
                                                   const TernaryInput<double> &,
                                                   float, double, RoundingMode);
template bool compare_ternary_operation_one_output(Operation,
                                                   const TernaryInput<double> &,
                                                   double, double,
                                                   RoundingMode);
template bool compare_ternary_operation_one_output(
    Operation, const TernaryInput<long double> &, float, double, RoundingMode);
template bool compare_ternary_operation_one_output(
    Operation, const TernaryInput<long double> &, double, double, RoundingMode);
template bool
compare_ternary_operation_one_output(Operation,
                                     const TernaryInput<long double> &,
                                     long double, double, RoundingMode);

#ifdef LIBC_TYPES_HAS_FLOAT16
template bool
compare_ternary_operation_one_output(Operation, const TernaryInput<float16> &,
                                     float16, double, RoundingMode);
template bool compare_ternary_operation_one_output(Operation,
                                                   const TernaryInput<float> &,
                                                   float16, double,
                                                   RoundingMode);
template bool compare_ternary_operation_one_output(Operation,
                                                   const TernaryInput<double> &,
                                                   float16, double,
                                                   RoundingMode);
template bool
compare_ternary_operation_one_output(Operation,
                                     const TernaryInput<long double> &, float16,
                                     double, RoundingMode);
#endif

template bool compare_ternary_operation_one_output(Operation,
                                                   const TernaryInput<float> &,
                                                   bfloat16, double,
                                                   RoundingMode);
template bool compare_ternary_operation_one_output(Operation,
                                                   const TernaryInput<double> &,
                                                   bfloat16, double,
                                                   RoundingMode);
template bool
compare_ternary_operation_one_output(Operation,
                                     const TernaryInput<long double> &,
                                     bfloat16, double, RoundingMode);

#if defined(LIBC_TYPES_HAS_FLOAT128) &&                                        \
    defined(LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE)
template bool
compare_ternary_operation_one_output(Operation, const TernaryInput<float128> &,
                                     bfloat16, double, RoundingMode);
#endif // defined(LIBC_TYPES_HAS_FLOAT128) &&
       // defined(LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE)

} // namespace internal

template <typename T> bool round_to_long(T x, long &result) {
  MPFRNumber mpfr(x);
  return mpfr.round_to_long(result);
}

template bool round_to_long<float>(float, long &);
template bool round_to_long<double>(double, long &);
template bool round_to_long<long double>(long double, long &);

#ifdef LIBC_TYPES_HAS_FLOAT16
template bool round_to_long<float16>(float16, long &);
#endif // LIBC_TYPES_HAS_FLOAT16

#ifdef LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE
template bool round_to_long<float128>(float128, long &);
#endif // LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE

template bool round_to_long<bfloat16>(bfloat16, long &);

template <typename T> bool round_to_long(T x, RoundingMode mode, long &result) {
  MPFRNumber mpfr(x);
  return mpfr.round_to_long(get_mpfr_rounding_mode(mode), result);
}

template bool round_to_long<float>(float, RoundingMode, long &);
template bool round_to_long<double>(double, RoundingMode, long &);
template bool round_to_long<long double>(long double, RoundingMode, long &);

#ifdef LIBC_TYPES_HAS_FLOAT16
template bool round_to_long<float16>(float16, RoundingMode, long &);
#endif // LIBC_TYPES_HAS_FLOAT16

#ifdef LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE
template bool round_to_long<float128>(float128, RoundingMode, long &);
#endif // LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE

template bool round_to_long<bfloat16>(bfloat16, RoundingMode, long &);

template <typename T> T round(T x, RoundingMode mode) {
  MPFRNumber mpfr(x);
  MPFRNumber result = mpfr.rint(get_mpfr_rounding_mode(mode));
  return result.as<T>();
}

template float round<float>(float, RoundingMode);
template double round<double>(double, RoundingMode);
template long double round<long double>(long double, RoundingMode);

#ifdef LIBC_TYPES_HAS_FLOAT16
template float16 round<float16>(float16, RoundingMode);
#endif // LIBC_TYPES_HAS_FLOAT16

#ifdef LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE
template float128 round<float128>(float128, RoundingMode);
#endif // LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE

template bfloat16 round<bfloat16>(bfloat16, RoundingMode);

} // namespace mpfr
} // namespace testing
} // namespace LIBC_NAMESPACE_DECL