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
18namespace LIBC_NAMESPACE {
19namespace testing {
20namespace mpfr {
21
22enum 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
94using LIBC_NAMESPACE::fputil::testing::ForceRoundingMode;
95using LIBC_NAMESPACE::fputil::testing::RoundingMode;
96
97template <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
106template <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
115template <typename T> struct BinaryOutput {
116 T f;
117 int i;
118};
119
120namespace internal {
121
122template <typename T1, typename T2>
123struct AreMatchingBinaryInputAndBinaryOutput {
124 static constexpr bool VALUE = false;
125};
126
127template <typename T>
128struct AreMatchingBinaryInputAndBinaryOutput<BinaryInput<T>, BinaryOutput<T>> {
129 static constexpr bool VALUE = cpp::is_floating_point_v<T>;
130};
131
132template <typename T>
133bool compare_unary_operation_single_output(Operation op, T input, T libc_output,
134 double ulp_tolerance,
135 RoundingMode rounding);
136template <typename T>
137bool compare_unary_operation_two_outputs(Operation op, T input,
138 const BinaryOutput<T> &libc_output,
139 double ulp_tolerance,
140 RoundingMode rounding);
141template <typename T>
142bool 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
148template <typename T>
149bool compare_binary_operation_one_output(Operation op,
150 const BinaryInput<T> &input,
151 T libc_output, double ulp_tolerance,
152 RoundingMode rounding);
153
154template <typename T>
155bool compare_ternary_operation_one_output(Operation op,
156 const TernaryInput<T> &input,
157 T libc_output, double ulp_tolerance,
158 RoundingMode rounding);
159
160template <typename T>
161void explain_unary_operation_single_output_error(Operation op, T input,
162 T match_value,
163 double ulp_tolerance,
164 RoundingMode rounding);
165template <typename T>
166void explain_unary_operation_two_outputs_error(
167 Operation op, T input, const BinaryOutput<T> &match_value,
168 double ulp_tolerance, RoundingMode rounding);
169template <typename T>
170void 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
175template <typename T>
176void 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
182template <typename T>
183void 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
189template <Operation op, bool silent, typename InputType, typename OutputType>
190class MPFRMatcher : public testing::Matcher<OutputType> {
191 InputType input;
192 OutputType match_value;
193 double ulp_tolerance;
194 RoundingMode rounding;
195
196public:
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
214private:
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.
272template <Operation op, typename InputType, typename OutputType>
273constexpr 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
296template <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>>
300get_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
306template <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>>
310get_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
316template <typename T> T round(T x, RoundingMode mode);
317
318template <typename T> bool round_to_long(T x, long &result);
319template <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

source code of libc/utils/MPFRWrapper/MPFRUtils.h