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-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #ifndef LLVM_LIBC_UTILS_TESTUTILS_MPFRUTILS_H
10 #define LLVM_LIBC_UTILS_TESTUTILS_MPFRUTILS_H
11 
12 #include "src/__support/CPP/TypeTraits.h"
13 #include "utils/UnitTest/Test.h"
14 
15 #include <stdint.h>
16 
17 namespace __llvm_libc {
18 namespace testing {
19 namespace mpfr {
20 
21 enum class Operation : int {
22   // Operations with take a single floating point number as input
23   // and produce a single floating point number as output. The input
24   // and output floating point numbers are of the same kind.
25   BeginUnaryOperationsSingleOutput,
26   Abs,
27   Ceil,
28   Cos,
29   Exp,
30   Exp2,
31   Expm1,
32   Floor,
33   Log,
34   Log2,
35   Log10,
36   Log1p,
37   Mod2PI,
38   ModPIOver2,
39   ModPIOver4,
40   Round,
41   Sin,
42   Sqrt,
43   Tan,
44   Trunc,
45   EndUnaryOperationsSingleOutput,
46 
47   // Operations which take a single floating point nubmer as input
48   // but produce two outputs. The first ouput is a floating point
49   // number of the same type as the input. The second output is of type
50   // 'int'.
51   BeginUnaryOperationsTwoOutputs,
52   Frexp, // Floating point output, the first output, is the fractional part.
53   EndUnaryOperationsTwoOutputs,
54 
55   // Operations wich take two floating point nubmers of the same type as
56   // input and produce a single floating point number of the same type as
57   // output.
58   BeginBinaryOperationsSingleOutput,
59   Hypot,
60   EndBinaryOperationsSingleOutput,
61 
62   // Operations which take two floating point numbers of the same type as
63   // input and produce two outputs. The first output is a floating nubmer of
64   // the same type as the inputs. The second output is af type 'int'.
65   BeginBinaryOperationsTwoOutputs,
66   RemQuo, // The first output, the floating point output, is the remainder.
67   EndBinaryOperationsTwoOutputs,
68 
69   // Operations which take three floating point nubmers of the same type as
70   // input and produce a single floating point number of the same type as
71   // output.
72   BeginTernaryOperationsSingleOuput,
73   Fma,
74   EndTernaryOperationsSingleOutput,
75 };
76 
77 enum class RoundingMode : uint8_t { Upward, Downward, TowardZero, Nearest };
78 
79 int get_fe_rounding(RoundingMode mode);
80 
81 struct ForceRoundingMode {
82   ForceRoundingMode(RoundingMode);
83   ~ForceRoundingMode();
84 
85   int old_rounding_mode;
86   int rounding_mode;
87 };
88 
89 template <typename T> struct BinaryInput {
90   static_assert(
91       __llvm_libc::cpp::IsFloatingPointType<T>::Value,
92       "Template parameter of BinaryInput must be a floating point type.");
93 
94   using Type = T;
95   T x, y;
96 };
97 
98 template <typename T> struct TernaryInput {
99   static_assert(
100       __llvm_libc::cpp::IsFloatingPointType<T>::Value,
101       "Template parameter of TernaryInput must be a floating point type.");
102 
103   using Type = T;
104   T x, y, z;
105 };
106 
107 template <typename T> struct BinaryOutput {
108   T f;
109   int i;
110 };
111 
112 namespace internal {
113 
114 template <typename T1, typename T2>
115 struct AreMatchingBinaryInputAndBinaryOutput {
116   static constexpr bool VALUE = false;
117 };
118 
119 template <typename T>
120 struct AreMatchingBinaryInputAndBinaryOutput<BinaryInput<T>, BinaryOutput<T>> {
121   static constexpr bool VALUE = cpp::IsFloatingPointType<T>::Value;
122 };
123 
124 template <typename T>
125 bool compare_unary_operation_single_output(Operation op, T input, T libc_output,
126                                            double ulp_tolerance,
127                                            RoundingMode rounding);
128 template <typename T>
129 bool compare_unary_operation_two_outputs(Operation op, T input,
130                                          const BinaryOutput<T> &libc_output,
131                                          double ulp_tolerance,
132                                          RoundingMode rounding);
133 template <typename T>
134 bool compare_binary_operation_two_outputs(Operation op,
135                                           const BinaryInput<T> &input,
136                                           const BinaryOutput<T> &libc_output,
137                                           double ulp_tolerance,
138                                           RoundingMode rounding);
139 
140 template <typename T>
141 bool compare_binary_operation_one_output(Operation op,
142                                          const BinaryInput<T> &input,
143                                          T libc_output, double ulp_tolerance,
144                                          RoundingMode rounding);
145 
146 template <typename T>
147 bool compare_ternary_operation_one_output(Operation op,
148                                           const TernaryInput<T> &input,
149                                           T libc_output, double ulp_tolerance,
150                                           RoundingMode rounding);
151 
152 template <typename T>
153 void explain_unary_operation_single_output_error(Operation op, T input,
154                                                  T match_value,
155                                                  double ulp_tolerance,
156                                                  RoundingMode rounding,
157                                                  testutils::StreamWrapper &OS);
158 template <typename T>
159 void explain_unary_operation_two_outputs_error(
160     Operation op, T input, const BinaryOutput<T> &match_value,
161     double ulp_tolerance, RoundingMode rounding, testutils::StreamWrapper &OS);
162 template <typename T>
163 void explain_binary_operation_two_outputs_error(
164     Operation op, const BinaryInput<T> &input,
165     const BinaryOutput<T> &match_value, double ulp_tolerance,
166     RoundingMode rounding, testutils::StreamWrapper &OS);
167 
168 template <typename T>
169 void explain_binary_operation_one_output_error(
170     Operation op, const BinaryInput<T> &input, T match_value,
171     double ulp_tolerance, RoundingMode rounding, testutils::StreamWrapper &OS);
172 
173 template <typename T>
174 void explain_ternary_operation_one_output_error(
175     Operation op, const TernaryInput<T> &input, T match_value,
176     double ulp_tolerance, RoundingMode rounding, testutils::StreamWrapper &OS);
177 
178 template <Operation op, typename InputType, typename OutputType>
179 class MPFRMatcher : public testing::Matcher<OutputType> {
180   InputType input;
181   OutputType match_value;
182   double ulp_tolerance;
183   RoundingMode rounding;
184 
185 public:
186   MPFRMatcher(InputType testInput, double ulp_tolerance, RoundingMode rounding)
187       : input(testInput), ulp_tolerance(ulp_tolerance), rounding(rounding) {}
188 
189   bool match(OutputType libcResult) {
190     match_value = libcResult;
191     return match(input, match_value);
192   }
193 
194   // This method is marked with NOLINT because it the name `explainError`
195   // does not confirm to the coding style.
196   void explainError(testutils::StreamWrapper &OS) override { // NOLINT
197     explain_error(input, match_value, OS);
198   }
199 
200 private:
201   template <typename T> bool match(T in, T out) {
202     return compare_unary_operation_single_output(op, in, out, ulp_tolerance,
203                                                  rounding);
204   }
205 
206   template <typename T> bool match(T in, const BinaryOutput<T> &out) {
207     return compare_unary_operation_two_outputs(op, in, out, ulp_tolerance,
208                                                rounding);
209   }
210 
211   template <typename T> bool match(const BinaryInput<T> &in, T out) {
212     return compare_binary_operation_one_output(op, in, out, ulp_tolerance,
213                                                rounding);
214   }
215 
216   template <typename T>
217   bool match(BinaryInput<T> in, const BinaryOutput<T> &out) {
218     return compare_binary_operation_two_outputs(op, in, out, ulp_tolerance,
219                                                 rounding);
220   }
221 
222   template <typename T> bool match(const TernaryInput<T> &in, T out) {
223     return compare_ternary_operation_one_output(op, in, out, ulp_tolerance,
224                                                 rounding);
225   }
226 
227   template <typename T>
228   void explain_error(T in, T out, testutils::StreamWrapper &OS) {
229     explain_unary_operation_single_output_error(op, in, out, ulp_tolerance,
230                                                 rounding, OS);
231   }
232 
233   template <typename T>
234   void explain_error(T in, const BinaryOutput<T> &out,
235                      testutils::StreamWrapper &OS) {
236     explain_unary_operation_two_outputs_error(op, in, out, ulp_tolerance,
237                                               rounding, OS);
238   }
239 
240   template <typename T>
241   void explain_error(const BinaryInput<T> &in, const BinaryOutput<T> &out,
242                      testutils::StreamWrapper &OS) {
243     explain_binary_operation_two_outputs_error(op, in, out, ulp_tolerance,
244                                                rounding, OS);
245   }
246 
247   template <typename T>
248   void explain_error(const BinaryInput<T> &in, T out,
249                      testutils::StreamWrapper &OS) {
250     explain_binary_operation_one_output_error(op, in, out, ulp_tolerance,
251                                               rounding, OS);
252   }
253 
254   template <typename T>
255   void explain_error(const TernaryInput<T> &in, T out,
256                      testutils::StreamWrapper &OS) {
257     explain_ternary_operation_one_output_error(op, in, out, ulp_tolerance,
258                                                rounding, OS);
259   }
260 };
261 
262 } // namespace internal
263 
264 // Return true if the input and ouput types for the operation op are valid
265 // types.
266 template <Operation op, typename InputType, typename OutputType>
267 constexpr bool is_valid_operation() {
268   return (Operation::BeginUnaryOperationsSingleOutput < op &&
269           op < Operation::EndUnaryOperationsSingleOutput &&
270           cpp::IsSame<InputType, OutputType>::Value &&
271           cpp::IsFloatingPointType<InputType>::Value) ||
272          (Operation::BeginUnaryOperationsTwoOutputs < op &&
273           op < Operation::EndUnaryOperationsTwoOutputs &&
274           cpp::IsFloatingPointType<InputType>::Value &&
275           cpp::IsSame<OutputType, BinaryOutput<InputType>>::Value) ||
276          (Operation::BeginBinaryOperationsSingleOutput < op &&
277           op < Operation::EndBinaryOperationsSingleOutput &&
278           cpp::IsFloatingPointType<OutputType>::Value &&
279           cpp::IsSame<InputType, BinaryInput<OutputType>>::Value) ||
280          (Operation::BeginBinaryOperationsTwoOutputs < op &&
281           op < Operation::EndBinaryOperationsTwoOutputs &&
282           internal::AreMatchingBinaryInputAndBinaryOutput<InputType,
283                                                           OutputType>::VALUE) ||
284          (Operation::BeginTernaryOperationsSingleOuput < op &&
285           op < Operation::EndTernaryOperationsSingleOutput &&
286           cpp::IsFloatingPointType<OutputType>::Value &&
287           cpp::IsSame<InputType, TernaryInput<OutputType>>::Value);
288 }
289 
290 template <Operation op, typename InputType, typename OutputType>
291 __attribute__((no_sanitize("address")))
292 cpp::EnableIfType<is_valid_operation<op, InputType, OutputType>(),
293                   internal::MPFRMatcher<op, InputType, OutputType>>
294 get_mpfr_matcher(InputType input, OutputType output_unused,
295                  double ulp_tolerance, RoundingMode rounding) {
296   return internal::MPFRMatcher<op, InputType, OutputType>(input, ulp_tolerance,
297                                                           rounding);
298 }
299 
300 template <typename T> T round(T x, RoundingMode mode);
301 
302 template <typename T> bool round_to_long(T x, long &result);
303 template <typename T> bool round_to_long(T x, RoundingMode mode, long &result);
304 
305 } // namespace mpfr
306 } // namespace testing
307 } // namespace __llvm_libc
308 
309 // GET_MPFR_DUMMY_ARG is going to be added to the end of GET_MPFR_MACRO as a
310 // simple way to avoid the compiler warning `gnu-zero-variadic-macro-arguments`.
311 #define GET_MPFR_DUMMY_ARG(...) 0
312 
313 #define GET_MPFR_MACRO(__1, __2, __3, __4, __5, __NAME, ...) __NAME
314 
315 #define EXPECT_MPFR_MATCH_DEFAULT(op, input, match_value, ulp_tolerance)       \
316   EXPECT_THAT(match_value,                                                     \
317               __llvm_libc::testing::mpfr::get_mpfr_matcher<op>(                \
318                   input, match_value, ulp_tolerance,                           \
319                   __llvm_libc::testing::mpfr::RoundingMode::Nearest))
320 
321 #define EXPECT_MPFR_MATCH_ROUNDING(op, input, match_value, ulp_tolerance,      \
322                                    rounding)                                   \
323   EXPECT_THAT(match_value, __llvm_libc::testing::mpfr::get_mpfr_matcher<op>(   \
324                                input, match_value, ulp_tolerance, rounding))
325 
326 #define EXPECT_MPFR_MATCH(...)                                                 \
327   GET_MPFR_MACRO(__VA_ARGS__, EXPECT_MPFR_MATCH_ROUNDING,                      \
328                  EXPECT_MPFR_MATCH_DEFAULT, GET_MPFR_DUMMY_ARG)                \
329   (__VA_ARGS__)
330 
331 #define EXPECT_MPFR_MATCH_ALL_ROUNDING(op, input, match_value, ulp_tolerance)  \
332   {                                                                            \
333     namespace mpfr = __llvm_libc::testing::mpfr;                               \
334     mpfr::ForceRoundingMode __r1(mpfr::RoundingMode::Nearest);                 \
335     EXPECT_MPFR_MATCH(op, input, match_value, ulp_tolerance,                   \
336                       mpfr::RoundingMode::Nearest);                            \
337     mpfr::ForceRoundingMode __r2(mpfr::RoundingMode::Upward);                  \
338     EXPECT_MPFR_MATCH(op, input, match_value, ulp_tolerance,                   \
339                       mpfr::RoundingMode::Upward);                             \
340     mpfr::ForceRoundingMode __r3(mpfr::RoundingMode::Downward);                \
341     EXPECT_MPFR_MATCH(op, input, match_value, ulp_tolerance,                   \
342                       mpfr::RoundingMode::Downward);                           \
343     mpfr::ForceRoundingMode __r4(mpfr::RoundingMode::TowardZero);              \
344     EXPECT_MPFR_MATCH(op, input, match_value, ulp_tolerance,                   \
345                       mpfr::RoundingMode::TowardZero);                         \
346   }
347 
348 #define ASSERT_MPFR_MATCH_DEFAULT(op, input, match_value, ulp_tolerance)       \
349   ASSERT_THAT(match_value,                                                     \
350               __llvm_libc::testing::mpfr::get_mpfr_matcher<op>(                \
351                   input, match_value, ulp_tolerance,                           \
352                   __llvm_libc::testing::mpfr::RoundingMode::Nearest))
353 
354 #define ASSERT_MPFR_MATCH_ROUNDING(op, input, match_value, ulp_tolerance,      \
355                                    rounding)                                   \
356   ASSERT_THAT(match_value, __llvm_libc::testing::mpfr::get_mpfr_matcher<op>(   \
357                                input, match_value, ulp_tolerance, rounding))
358 
359 #define ASSERT_MPFR_MATCH(...)                                                 \
360   GET_MPFR_MACRO(__VA_ARGS__, ASSERT_MPFR_MATCH_ROUNDING,                      \
361                  ASSERT_MPFR_MATCH_DEFAULT, GET_MPFR_DUMMY_ARG)                \
362   (__VA_ARGS__)
363 
364 #define ASSERT_MPFR_MATCH_ALL_ROUNDING(op, input, match_value, ulp_tolerance)  \
365   {                                                                            \
366     namespace mpfr = __llvm_libc::testing::mpfr;                               \
367     mpfr::ForceRoundingMode __r1(mpfr::RoundingMode::Nearest);                 \
368     ASSERT_MPFR_MATCH(op, input, match_value, ulp_tolerance,                   \
369                       mpfr::RoundingMode::Nearest);                            \
370     mpfr::ForceRoundingMode __r2(mpfr::RoundingMode::Upward);                  \
371     ASSERT_MPFR_MATCH(op, input, match_value, ulp_tolerance,                   \
372                       mpfr::RoundingMode::Upward);                             \
373     mpfr::ForceRoundingMode __r3(mpfr::RoundingMode::Downward);                \
374     ASSERT_MPFR_MATCH(op, input, match_value, ulp_tolerance,                   \
375                       mpfr::RoundingMode::Downward);                           \
376     mpfr::ForceRoundingMode __r4(mpfr::RoundingMode::TowardZero);              \
377     ASSERT_MPFR_MATCH(op, input, match_value, ulp_tolerance,                   \
378                       mpfr::RoundingMode::TowardZero);                         \
379   }
380 
381 #endif // LLVM_LIBC_UTILS_TESTUTILS_MPFRUTILS_H
382