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