492 lines · c
1//===-- MPFRUtils.h ---------------------------------------------*- C++ -*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#ifndef LLVM_LIBC_UTILS_MPFRWRAPPER_MPFRUTILS_H10#define LLVM_LIBC_UTILS_MPFRWRAPPER_MPFRUTILS_H11 12#include "hdr/stdint_proxy.h"13#include "src/__support/CPP/type_traits.h"14#include "src/__support/macros/config.h"15#include "test/UnitTest/RoundingModeUtils.h"16#include "test/UnitTest/Test.h"17 18namespace LIBC_NAMESPACE_DECL {19namespace testing {20namespace mpfr {21 22enum class Operation : int {23 // Operations which take a single floating point number as input24 // and produce a single floating point number as output. The input25 // and output floating point numbers are of the same kind.26 BeginUnaryOperationsSingleOutput,27 Abs,28 Acos,29 Acosh,30 Acospi,31 Asin,32 Asinh,33 Asinpi,34 Atan,35 Atanh,36 Atanpi,37 Cbrt,38 Ceil,39 Cos,40 Cosh,41 Cospi,42 Erf,43 Exp,44 Exp2,45 Exp2m1,46 Exp10,47 Exp10m1,48 Expm1,49 Floor,50 Log,51 Log2,52 Log10,53 Log1p,54 Mod2PI,55 ModPIOver2,56 ModPIOver4,57 Round,58 RoundEven,59 Rsqrt,60 Sin,61 Sinpi,62 Sinh,63 Sqrt,64 Tan,65 Tanh,66 Tanpi,67 Trunc,68 EndUnaryOperationsSingleOutput,69 70 // Operations which take a single floating point nubmer as input71 // but produce two outputs. The first ouput is a floating point72 // number of the same type as the input. The second output is of type73 // 'int'.74 BeginUnaryOperationsTwoOutputs,75 Frexp, // Floating point output, the first output, is the fractional part.76 EndUnaryOperationsTwoOutputs,77 78 // Operations wich take two floating point nubmers of the same type as79 // input and produce a single floating point number of the same type as80 // output.81 BeginBinaryOperationsSingleOutput,82 Add,83 Atan2,84 Div,85 Fmod,86 Hypot,87 Mul,88 Pow,89 Sub,90 EndBinaryOperationsSingleOutput,91 92 // Operations which take two floating point numbers of the same type as93 // input and produce two outputs. The first output is a floating point number94 // of the same type as the inputs. The second output is of type 'int'.95 BeginBinaryOperationsTwoOutputs,96 RemQuo, // The first output(floating point) is the remainder.97 EndBinaryOperationsTwoOutputs,98 99 // Operations which take three floating point nubmers of the same type as100 // input and produce a single floating point number of the same type as101 // output.102 BeginTernaryOperationsSingleOuput,103 Fma,104 EndTernaryOperationsSingleOutput,105};106 107using LIBC_NAMESPACE::fputil::testing::ForceRoundingMode;108using LIBC_NAMESPACE::fputil::testing::RoundingMode;109 110template <typename T> struct BinaryInput {111 static_assert(112 LIBC_NAMESPACE::cpp::is_floating_point_v<T>,113 "Template parameter of BinaryInput must be a floating point type.");114 115 using Type = T;116 T x, y;117};118 119template <typename T> struct TernaryInput {120 static_assert(121 LIBC_NAMESPACE::cpp::is_floating_point_v<T>,122 "Template parameter of TernaryInput must be a floating point type.");123 124 using Type = T;125 T x, y, z;126};127 128template <typename T> struct BinaryOutput {129 T f;130 int i;131};132 133namespace internal {134 135template <typename T1, typename T2>136struct AreMatchingBinaryInputAndBinaryOutput {137 static constexpr bool VALUE = false;138};139 140template <typename T>141struct AreMatchingBinaryInputAndBinaryOutput<BinaryInput<T>, BinaryOutput<T>> {142 static constexpr bool VALUE = cpp::is_floating_point_v<T>;143};144 145template <typename T> struct IsBinaryInput {146 static constexpr bool VALUE = false;147};148 149template <typename T> struct IsBinaryInput<BinaryInput<T>> {150 static constexpr bool VALUE = true;151};152 153template <typename T> struct IsTernaryInput {154 static constexpr bool VALUE = false;155};156 157template <typename T> struct IsTernaryInput<TernaryInput<T>> {158 static constexpr bool VALUE = true;159};160 161template <typename T> struct MakeScalarInput : cpp::type_identity<T> {};162 163template <typename T>164struct MakeScalarInput<BinaryInput<T>> : cpp::type_identity<T> {};165 166template <typename T>167struct MakeScalarInput<TernaryInput<T>> : cpp::type_identity<T> {};168 169template <typename InputType, typename OutputType>170bool compare_unary_operation_single_output(Operation op, InputType input,171 OutputType libc_output,172 double ulp_tolerance,173 RoundingMode rounding);174template <typename T>175bool compare_unary_operation_two_outputs(Operation op, T input,176 const BinaryOutput<T> &libc_output,177 double ulp_tolerance,178 RoundingMode rounding);179template <typename T>180bool compare_binary_operation_two_outputs(Operation op,181 const BinaryInput<T> &input,182 const BinaryOutput<T> &libc_output,183 double ulp_tolerance,184 RoundingMode rounding);185 186template <typename InputType, typename OutputType>187bool compare_binary_operation_one_output(Operation op,188 const BinaryInput<InputType> &input,189 OutputType libc_output,190 double ulp_tolerance,191 RoundingMode rounding);192 193template <typename InputType, typename OutputType>194bool compare_ternary_operation_one_output(Operation op,195 const TernaryInput<InputType> &input,196 OutputType libc_output,197 double ulp_tolerance,198 RoundingMode rounding);199 200template <typename InputType, typename OutputType>201void explain_unary_operation_single_output_error(Operation op, InputType input,202 OutputType match_value,203 double ulp_tolerance,204 RoundingMode rounding);205template <typename T>206void explain_unary_operation_two_outputs_error(207 Operation op, T input, const BinaryOutput<T> &match_value,208 double ulp_tolerance, RoundingMode rounding);209template <typename T>210void explain_binary_operation_two_outputs_error(211 Operation op, const BinaryInput<T> &input,212 const BinaryOutput<T> &match_value, double ulp_tolerance,213 RoundingMode rounding);214 215template <typename InputType, typename OutputType>216void explain_binary_operation_one_output_error(217 Operation op, const BinaryInput<InputType> &input, OutputType match_value,218 double ulp_tolerance, RoundingMode rounding);219 220template <typename InputType, typename OutputType>221void explain_ternary_operation_one_output_error(222 Operation op, const TernaryInput<InputType> &input, OutputType match_value,223 double ulp_tolerance, RoundingMode rounding);224 225template <Operation op, bool silent, typename InputType, typename OutputType>226class MPFRMatcher : public testing::Matcher<OutputType> {227 InputType input;228 OutputType match_value;229 double ulp_tolerance;230 RoundingMode rounding;231 232public:233 MPFRMatcher(InputType testInput, double ulp_tolerance, RoundingMode rounding)234 : input(testInput), ulp_tolerance(ulp_tolerance), rounding(rounding) {}235 236 bool match(OutputType libcResult) {237 match_value = libcResult;238 return match(input, match_value);239 }240 241 // This method is marked with NOLINT because the name `explainError` does not242 // conform to the coding style.243 void explainError() override { // NOLINT244 explain_error(input, match_value);245 }246 247 // Whether the `explainError` step is skipped or not.248 bool is_silent() const override { return silent; }249 250private:251 template <typename InType, typename OutType>252 bool match(InType in, OutType out) {253 return compare_unary_operation_single_output(op, in, out, ulp_tolerance,254 rounding);255 }256 257 template <typename T> bool match(T in, const BinaryOutput<T> &out) {258 return compare_unary_operation_two_outputs(op, in, out, ulp_tolerance,259 rounding);260 }261 262 template <typename T, typename U>263 bool match(const BinaryInput<T> &in, U out) {264 return compare_binary_operation_one_output(op, in, out, ulp_tolerance,265 rounding);266 }267 268 template <typename T>269 bool match(BinaryInput<T> in, const BinaryOutput<T> &out) {270 return compare_binary_operation_two_outputs(op, in, out, ulp_tolerance,271 rounding);272 }273 274 template <typename InType, typename OutType>275 bool match(const TernaryInput<InType> &in, OutType out) {276 return compare_ternary_operation_one_output(op, in, out, ulp_tolerance,277 rounding);278 }279 280 template <typename InType, typename OutType>281 void explain_error(InType in, OutType out) {282 explain_unary_operation_single_output_error(op, in, out, ulp_tolerance,283 rounding);284 }285 286 template <typename T> void explain_error(T in, const BinaryOutput<T> &out) {287 explain_unary_operation_two_outputs_error(op, in, out, ulp_tolerance,288 rounding);289 }290 291 template <typename T>292 void explain_error(const BinaryInput<T> &in, const BinaryOutput<T> &out) {293 explain_binary_operation_two_outputs_error(op, in, out, ulp_tolerance,294 rounding);295 }296 297 template <typename T, typename U>298 void explain_error(const BinaryInput<T> &in, U out) {299 explain_binary_operation_one_output_error(op, in, out, ulp_tolerance,300 rounding);301 }302 303 template <typename InType, typename OutType>304 void explain_error(const TernaryInput<InType> &in, OutType out) {305 explain_ternary_operation_one_output_error(op, in, out, ulp_tolerance,306 rounding);307 }308};309 310} // namespace internal311 312// Return true if the input and ouput types for the operation op are valid313// types.314template <Operation op, typename InputType, typename OutputType>315constexpr bool is_valid_operation() {316 constexpr bool IS_NARROWING_OP =317 (op == Operation::Sqrt && cpp::is_floating_point_v<InputType> &&318 cpp::is_floating_point_v<OutputType> &&319 sizeof(OutputType) <= sizeof(InputType)) ||320 (Operation::BeginBinaryOperationsSingleOutput < op &&321 op < Operation::EndBinaryOperationsSingleOutput &&322 internal::IsBinaryInput<InputType>::VALUE &&323 cpp::is_floating_point_v<324 typename internal::MakeScalarInput<InputType>::type> &&325 cpp::is_floating_point_v<OutputType>) ||326 (op == Operation::Fma && internal::IsTernaryInput<InputType>::VALUE &&327 cpp::is_floating_point_v<328 typename internal::MakeScalarInput<InputType>::type> &&329 cpp::is_floating_point_v<OutputType>);330 if (IS_NARROWING_OP)331 return true;332 return (Operation::BeginUnaryOperationsSingleOutput < op &&333 op < Operation::EndUnaryOperationsSingleOutput &&334 cpp::is_same_v<InputType, OutputType> &&335 cpp::is_floating_point_v<InputType>) ||336 (Operation::BeginUnaryOperationsTwoOutputs < op &&337 op < Operation::EndUnaryOperationsTwoOutputs &&338 cpp::is_floating_point_v<InputType> &&339 cpp::is_same_v<OutputType, BinaryOutput<InputType>>) ||340 (Operation::BeginBinaryOperationsSingleOutput < op &&341 op < Operation::EndBinaryOperationsSingleOutput &&342 cpp::is_floating_point_v<OutputType> &&343 cpp::is_same_v<InputType, BinaryInput<OutputType>>) ||344 (Operation::BeginBinaryOperationsTwoOutputs < op &&345 op < Operation::EndBinaryOperationsTwoOutputs &&346 internal::AreMatchingBinaryInputAndBinaryOutput<InputType,347 OutputType>::VALUE) ||348 (Operation::BeginTernaryOperationsSingleOuput < op &&349 op < Operation::EndTernaryOperationsSingleOutput &&350 cpp::is_floating_point_v<OutputType> &&351 cpp::is_same_v<InputType, TernaryInput<OutputType>>);352}353 354template <Operation op, typename InputType, typename OutputType>355__attribute__((no_sanitize("address"))) cpp::enable_if_t<356 is_valid_operation<op, InputType, OutputType>(),357 internal::MPFRMatcher<op, /*is_silent*/ false, InputType, OutputType>>358get_mpfr_matcher(InputType input, [[maybe_unused]] OutputType output_unused,359 double ulp_tolerance, RoundingMode rounding) {360 return internal::MPFRMatcher<op, /*is_silent*/ false, InputType, OutputType>(361 input, ulp_tolerance, rounding);362}363 364template <Operation op, typename InputType, typename OutputType>365__attribute__((no_sanitize("address"))) cpp::enable_if_t<366 is_valid_operation<op, InputType, OutputType>(),367 internal::MPFRMatcher<op, /*is_silent*/ true, InputType, OutputType>>368get_silent_mpfr_matcher(InputType input,369 [[maybe_unused]] OutputType output_unused,370 double ulp_tolerance, RoundingMode rounding) {371 return internal::MPFRMatcher<op, /*is_silent*/ true, InputType, OutputType>(372 input, ulp_tolerance, rounding);373}374 375template <typename T> T round(T x, RoundingMode mode);376 377template <typename T> bool round_to_long(T x, long &result);378template <typename T> bool round_to_long(T x, RoundingMode mode, long &result);379 380} // namespace mpfr381} // namespace testing382} // namespace LIBC_NAMESPACE_DECL383 384// GET_MPFR_DUMMY_ARG is going to be added to the end of GET_MPFR_MACRO as a385// simple way to avoid the compiler warning `gnu-zero-variadic-macro-arguments`.386#define GET_MPFR_DUMMY_ARG(...) 0387 388#define GET_MPFR_MACRO(__1, __2, __3, __4, __5, __NAME, ...) __NAME389 390#define EXPECT_MPFR_MATCH_DEFAULT(op, input, match_value, ulp_tolerance) \391 EXPECT_THAT(match_value, \392 LIBC_NAMESPACE::testing::mpfr::get_mpfr_matcher<op>( \393 input, match_value, ulp_tolerance, \394 LIBC_NAMESPACE::testing::mpfr::RoundingMode::Nearest))395 396#define EXPECT_MPFR_MATCH_ROUNDING(op, input, match_value, ulp_tolerance, \397 rounding) \398 EXPECT_THAT(match_value, \399 LIBC_NAMESPACE::testing::mpfr::get_mpfr_matcher<op>( \400 input, match_value, ulp_tolerance, rounding))401 402#define EXPECT_MPFR_MATCH(...) \403 GET_MPFR_MACRO(__VA_ARGS__, EXPECT_MPFR_MATCH_ROUNDING, \404 EXPECT_MPFR_MATCH_DEFAULT, GET_MPFR_DUMMY_ARG) \405 (__VA_ARGS__)406 407#define TEST_MPFR_MATCH_ROUNDING(op, input, match_value, ulp_tolerance, \408 rounding) \409 LIBC_NAMESPACE::testing::mpfr::get_mpfr_matcher<op>(input, match_value, \410 ulp_tolerance, rounding) \411 .match(match_value)412 413#define TEST_MPFR_MATCH(...) \414 GET_MPFR_MACRO(__VA_ARGS__, TEST_MPFR_MATCH_ROUNDING, \415 EXPECT_MPFR_MATCH_DEFAULT, GET_MPFR_DUMMY_ARG) \416 (__VA_ARGS__)417 418#define EXPECT_MPFR_MATCH_ALL_ROUNDING(op, input, match_value, ulp_tolerance) \419 { \420 namespace mpfr = LIBC_NAMESPACE::testing::mpfr; \421 mpfr::ForceRoundingMode __r1(mpfr::RoundingMode::Nearest); \422 if (__r1.success) { \423 EXPECT_MPFR_MATCH(op, input, match_value, ulp_tolerance, \424 mpfr::RoundingMode::Nearest); \425 } \426 mpfr::ForceRoundingMode __r2(mpfr::RoundingMode::Upward); \427 if (__r2.success) { \428 EXPECT_MPFR_MATCH(op, input, match_value, ulp_tolerance, \429 mpfr::RoundingMode::Upward); \430 } \431 mpfr::ForceRoundingMode __r3(mpfr::RoundingMode::Downward); \432 if (__r3.success) { \433 EXPECT_MPFR_MATCH(op, input, match_value, ulp_tolerance, \434 mpfr::RoundingMode::Downward); \435 } \436 mpfr::ForceRoundingMode __r4(mpfr::RoundingMode::TowardZero); \437 if (__r4.success) { \438 EXPECT_MPFR_MATCH(op, input, match_value, ulp_tolerance, \439 mpfr::RoundingMode::TowardZero); \440 } \441 }442 443#define TEST_MPFR_MATCH_ROUNDING_SILENTLY(op, input, match_value, \444 ulp_tolerance, rounding) \445 LIBC_NAMESPACE::testing::mpfr::get_silent_mpfr_matcher<op>( \446 input, match_value, ulp_tolerance, rounding) \447 .match(match_value)448 449#define ASSERT_MPFR_MATCH_DEFAULT(op, input, match_value, ulp_tolerance) \450 ASSERT_THAT(match_value, \451 LIBC_NAMESPACE::testing::mpfr::get_mpfr_matcher<op>( \452 input, match_value, ulp_tolerance, \453 LIBC_NAMESPACE::testing::mpfr::RoundingMode::Nearest))454 455#define ASSERT_MPFR_MATCH_ROUNDING(op, input, match_value, ulp_tolerance, \456 rounding) \457 ASSERT_THAT(match_value, \458 LIBC_NAMESPACE::testing::mpfr::get_mpfr_matcher<op>( \459 input, match_value, ulp_tolerance, rounding))460 461#define ASSERT_MPFR_MATCH(...) \462 GET_MPFR_MACRO(__VA_ARGS__, ASSERT_MPFR_MATCH_ROUNDING, \463 ASSERT_MPFR_MATCH_DEFAULT, GET_MPFR_DUMMY_ARG) \464 (__VA_ARGS__)465 466#define ASSERT_MPFR_MATCH_ALL_ROUNDING(op, input, match_value, ulp_tolerance) \467 { \468 namespace mpfr = LIBC_NAMESPACE::testing::mpfr; \469 mpfr::ForceRoundingMode __r1(mpfr::RoundingMode::Nearest); \470 if (__r1.success) { \471 ASSERT_MPFR_MATCH(op, input, match_value, ulp_tolerance, \472 mpfr::RoundingMode::Nearest); \473 } \474 mpfr::ForceRoundingMode __r2(mpfr::RoundingMode::Upward); \475 if (__r2.success) { \476 ASSERT_MPFR_MATCH(op, input, match_value, ulp_tolerance, \477 mpfr::RoundingMode::Upward); \478 } \479 mpfr::ForceRoundingMode __r3(mpfr::RoundingMode::Downward); \480 if (__r3.success) { \481 ASSERT_MPFR_MATCH(op, input, match_value, ulp_tolerance, \482 mpfr::RoundingMode::Downward); \483 } \484 mpfr::ForceRoundingMode __r4(mpfr::RoundingMode::TowardZero); \485 if (__r4.success) { \486 ASSERT_MPFR_MATCH(op, input, match_value, ulp_tolerance, \487 mpfr::RoundingMode::TowardZero); \488 } \489 }490 491#endif // LLVM_LIBC_UTILS_MPFRWRAPPER_MPFRUTILS_H492