1 //===-- Utils which wrap MPFR ---------------------------------------------===//
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 #include "MPFRUtils.h"
10
11 #include "utils/FPUtil/FPBits.h"
12 #include "utils/FPUtil/TestHelpers.h"
13
14 #include "llvm/ADT/StringExtras.h"
15 #include "llvm/ADT/StringRef.h"
16
17 #include <memory>
18 #include <stdint.h>
19 #include <string>
20
21 #ifdef CUSTOM_MPFR_INCLUDER
22 // Some downstream repos are monoliths carrying MPFR sources in their third
23 // party directory. In such repos, including the MPFR header as
24 // `#include <mpfr.h>` is either disallowed or not possible. If that is the
25 // case, a file named `CustomMPFRIncluder.h` should be added through which the
26 // MPFR header can be included in manner allowed in that repo.
27 #include "CustomMPFRIncluder.h"
28 #else
29 #include <mpfr.h>
30 #endif
31
32 template <typename T> using FPBits = __llvm_libc::fputil::FPBits<T>;
33
34 namespace __llvm_libc {
35 namespace testing {
36 namespace mpfr {
37
38 class MPFRNumber {
39 // A precision value which allows sufficiently large additional
40 // precision even compared to quad-precision floating point values.
41 static constexpr unsigned int mpfrPrecision = 128;
42
43 mpfr_t value;
44
45 public:
MPFRNumber()46 MPFRNumber() { mpfr_init2(value, mpfrPrecision); }
47
48 // We use explicit EnableIf specializations to disallow implicit
49 // conversions. Implicit conversions can potentially lead to loss of
50 // precision.
51 template <typename XType,
52 cpp::EnableIfType<cpp::IsSame<float, XType>::Value, int> = 0>
MPFRNumber(XType x)53 explicit MPFRNumber(XType x) {
54 mpfr_init2(value, mpfrPrecision);
55 mpfr_set_flt(value, x, MPFR_RNDN);
56 }
57
58 template <typename XType,
59 cpp::EnableIfType<cpp::IsSame<double, XType>::Value, int> = 0>
MPFRNumber(XType x)60 explicit MPFRNumber(XType x) {
61 mpfr_init2(value, mpfrPrecision);
62 mpfr_set_d(value, x, MPFR_RNDN);
63 }
64
65 template <typename XType,
66 cpp::EnableIfType<cpp::IsSame<long double, XType>::Value, int> = 0>
MPFRNumber(XType x)67 explicit MPFRNumber(XType x) {
68 mpfr_init2(value, mpfrPrecision);
69 mpfr_set_ld(value, x, MPFR_RNDN);
70 }
71
72 template <typename XType,
73 cpp::EnableIfType<cpp::IsIntegral<XType>::Value, int> = 0>
MPFRNumber(XType x)74 explicit MPFRNumber(XType x) {
75 mpfr_init2(value, mpfrPrecision);
76 mpfr_set_sj(value, x, MPFR_RNDN);
77 }
78
MPFRNumber(const MPFRNumber & other)79 MPFRNumber(const MPFRNumber &other) {
80 mpfr_set(value, other.value, MPFR_RNDN);
81 }
82
~MPFRNumber()83 ~MPFRNumber() {
84 mpfr_clear(value);
85 }
86
operator =(const MPFRNumber & rhs)87 MPFRNumber &operator=(const MPFRNumber &rhs) {
88 mpfr_set(value, rhs.value, MPFR_RNDN);
89 return *this;
90 }
91
abs() const92 MPFRNumber abs() const {
93 MPFRNumber result;
94 mpfr_abs(result.value, value, MPFR_RNDN);
95 return result;
96 }
97
ceil() const98 MPFRNumber ceil() const {
99 MPFRNumber result;
100 mpfr_ceil(result.value, value);
101 return result;
102 }
103
cos() const104 MPFRNumber cos() const {
105 MPFRNumber result;
106 mpfr_cos(result.value, value, MPFR_RNDN);
107 return result;
108 }
109
exp() const110 MPFRNumber exp() const {
111 MPFRNumber result;
112 mpfr_exp(result.value, value, MPFR_RNDN);
113 return result;
114 }
115
exp2() const116 MPFRNumber exp2() const {
117 MPFRNumber result;
118 mpfr_exp2(result.value, value, MPFR_RNDN);
119 return result;
120 }
121
floor() const122 MPFRNumber floor() const {
123 MPFRNumber result;
124 mpfr_floor(result.value, value);
125 return result;
126 }
127
frexp(int & exp)128 MPFRNumber frexp(int &exp) {
129 MPFRNumber result;
130 mpfr_exp_t resultExp;
131 mpfr_frexp(&resultExp, result.value, value, MPFR_RNDN);
132 exp = resultExp;
133 return result;
134 }
135
hypot(const MPFRNumber & b)136 MPFRNumber hypot(const MPFRNumber &b) {
137 MPFRNumber result;
138 mpfr_hypot(result.value, value, b.value, MPFR_RNDN);
139 return result;
140 }
141
remquo(const MPFRNumber & divisor,int & quotient)142 MPFRNumber remquo(const MPFRNumber &divisor, int "ient) {
143 MPFRNumber remainder;
144 long q;
145 mpfr_remquo(remainder.value, &q, value, divisor.value, MPFR_RNDN);
146 quotient = q;
147 return remainder;
148 }
149
round() const150 MPFRNumber round() const {
151 MPFRNumber result;
152 mpfr_round(result.value, value);
153 return result;
154 }
155
sin() const156 MPFRNumber sin() const {
157 MPFRNumber result;
158 mpfr_sin(result.value, value, MPFR_RNDN);
159 return result;
160 }
161
sqrt() const162 MPFRNumber sqrt() const {
163 MPFRNumber result;
164 mpfr_sqrt(result.value, value, MPFR_RNDN);
165 return result;
166 }
167
trunc() const168 MPFRNumber trunc() const {
169 MPFRNumber result;
170 mpfr_trunc(result.value, value);
171 return result;
172 }
173
str() const174 std::string str() const {
175 // 200 bytes should be more than sufficient to hold a 100-digit number
176 // plus additional bytes for the decimal point, '-' sign etc.
177 constexpr size_t printBufSize = 200;
178 char buffer[printBufSize];
179 mpfr_snprintf(buffer, printBufSize, "%100.50Rf", value);
180 llvm::StringRef ref(buffer);
181 ref = ref.trim();
182 return ref.str();
183 }
184
185 // These functions are useful for debugging.
186 template <typename T> T as() const;
187
as() const188 template <> float as<float>() const { return mpfr_get_flt(value, MPFR_RNDN); }
as() const189 template <> double as<double>() const { return mpfr_get_d(value, MPFR_RNDN); }
as() const190 template <> long double as<long double>() const {
191 return mpfr_get_ld(value, MPFR_RNDN);
192 }
193
dump(const char * msg) const194 void dump(const char *msg) const { mpfr_printf("%s%.128Rf\n", msg, value); }
195
196 // Return the ULP (units-in-the-last-place) difference between the
197 // stored MPFR and a floating point number.
198 //
199 // We define:
200 // ULP(mpfr_value, value) = abs(mpfr_value - value) / eps(value)
201 //
202 // Remarks:
203 // 1. ULP < 0.5 will imply that the value is correctly rounded.
204 // 2. We expect that this value and the value to be compared (the [input]
205 // argument) are reasonable close, and we will provide an upper bound
206 // of ULP value for testing. Morever, most of the fractional parts of
207 // ULP value do not matter much, so using double as the return type
208 // should be good enough.
209 template <typename T>
ulp(T input)210 cpp::EnableIfType<cpp::IsFloatingPointType<T>::Value, double> ulp(T input) {
211 fputil::FPBits<T> bits(input);
212 MPFRNumber mpfrInput(input);
213
214 // abs(value - input)
215 mpfr_sub(mpfrInput.value, value, mpfrInput.value, MPFR_RNDN);
216 mpfr_abs(mpfrInput.value, mpfrInput.value, MPFR_RNDN);
217
218 // get eps(input)
219 int epsExponent = bits.exponent - fputil::FPBits<T>::exponentBias -
220 fputil::MantissaWidth<T>::value;
221 if (bits.exponent == 0) {
222 // correcting denormal exponent
223 ++epsExponent;
224 } else if ((bits.mantissa == 0) && (bits.exponent > 1) &&
225 mpfr_less_p(value, mpfrInput.value)) {
226 // when the input is exactly 2^n, distance (epsilon) between the input
227 // and the next floating point number is different from the distance to
228 // the previous floating point number. So in that case, if the correct
229 // value from MPFR is smaller than the input, we use the smaller epsilon
230 --epsExponent;
231 }
232
233 // Since eps(value) is of the form 2^e, instead of dividing such number,
234 // we multiply by its inverse 2^{-e}.
235 mpfr_mul_2si(mpfrInput.value, mpfrInput.value, -epsExponent, MPFR_RNDN);
236
237 return mpfrInput.as<double>();
238 }
239 };
240
241 namespace internal {
242
243 template <typename InputType>
244 cpp::EnableIfType<cpp::IsFloatingPointType<InputType>::Value, MPFRNumber>
unaryOperation(Operation op,InputType input)245 unaryOperation(Operation op, InputType input) {
246 MPFRNumber mpfrInput(input);
247 switch (op) {
248 case Operation::Abs:
249 return mpfrInput.abs();
250 case Operation::Ceil:
251 return mpfrInput.ceil();
252 case Operation::Cos:
253 return mpfrInput.cos();
254 case Operation::Exp:
255 return mpfrInput.exp();
256 case Operation::Exp2:
257 return mpfrInput.exp2();
258 case Operation::Floor:
259 return mpfrInput.floor();
260 case Operation::Round:
261 return mpfrInput.round();
262 case Operation::Sin:
263 return mpfrInput.sin();
264 case Operation::Sqrt:
265 return mpfrInput.sqrt();
266 case Operation::Trunc:
267 return mpfrInput.trunc();
268 default:
269 __builtin_unreachable();
270 }
271 }
272
273 template <typename InputType>
274 cpp::EnableIfType<cpp::IsFloatingPointType<InputType>::Value, MPFRNumber>
unaryOperationTwoOutputs(Operation op,InputType input,int & output)275 unaryOperationTwoOutputs(Operation op, InputType input, int &output) {
276 MPFRNumber mpfrInput(input);
277 switch (op) {
278 case Operation::Frexp:
279 return mpfrInput.frexp(output);
280 default:
281 __builtin_unreachable();
282 }
283 }
284
285 template <typename InputType>
286 cpp::EnableIfType<cpp::IsFloatingPointType<InputType>::Value, MPFRNumber>
binaryOperationOneOutput(Operation op,InputType x,InputType y)287 binaryOperationOneOutput(Operation op, InputType x, InputType y) {
288 MPFRNumber inputX(x), inputY(y);
289 switch (op) {
290 case Operation::Hypot:
291 return inputX.hypot(inputY);
292 default:
293 __builtin_unreachable();
294 }
295 }
296
297 template <typename InputType>
298 cpp::EnableIfType<cpp::IsFloatingPointType<InputType>::Value, MPFRNumber>
binaryOperationTwoOutputs(Operation op,InputType x,InputType y,int & output)299 binaryOperationTwoOutputs(Operation op, InputType x, InputType y, int &output) {
300 MPFRNumber inputX(x), inputY(y);
301 switch (op) {
302 case Operation::RemQuo:
303 return inputX.remquo(inputY, output);
304 default:
305 __builtin_unreachable();
306 }
307 }
308
309 template <typename T>
explainUnaryOperationSingleOutputError(Operation op,T input,T matchValue,testutils::StreamWrapper & OS)310 void explainUnaryOperationSingleOutputError(Operation op, T input, T matchValue,
311 testutils::StreamWrapper &OS) {
312 MPFRNumber mpfrInput(input);
313 MPFRNumber mpfrResult = unaryOperation(op, input);
314 MPFRNumber mpfrMatchValue(matchValue);
315 FPBits<T> inputBits(input);
316 FPBits<T> matchBits(matchValue);
317 FPBits<T> mpfrResultBits(mpfrResult.as<T>());
318 OS << "Match value not within tolerance value of MPFR result:\n"
319 << " Input decimal: " << mpfrInput.str() << '\n';
320 __llvm_libc::fputil::testing::describeValue(" Input bits: ", input, OS);
321 OS << '\n' << " Match decimal: " << mpfrMatchValue.str() << '\n';
322 __llvm_libc::fputil::testing::describeValue(" Match bits: ", matchValue,
323 OS);
324 OS << '\n' << " MPFR result: " << mpfrResult.str() << '\n';
325 __llvm_libc::fputil::testing::describeValue(
326 " MPFR rounded: ", mpfrResult.as<T>(), OS);
327 OS << '\n';
328 OS << " ULP error: " << std::to_string(mpfrResult.ulp(matchValue))
329 << '\n';
330 }
331
332 template void
333 explainUnaryOperationSingleOutputError<float>(Operation op, float, float,
334 testutils::StreamWrapper &);
335 template void
336 explainUnaryOperationSingleOutputError<double>(Operation op, double, double,
337 testutils::StreamWrapper &);
338 template void explainUnaryOperationSingleOutputError<long double>(
339 Operation op, long double, long double, testutils::StreamWrapper &);
340
341 template <typename T>
explainUnaryOperationTwoOutputsError(Operation op,T input,const BinaryOutput<T> & libcResult,testutils::StreamWrapper & OS)342 void explainUnaryOperationTwoOutputsError(Operation op, T input,
343 const BinaryOutput<T> &libcResult,
344 testutils::StreamWrapper &OS) {
345 MPFRNumber mpfrInput(input);
346 FPBits<T> inputBits(input);
347 int mpfrIntResult;
348 MPFRNumber mpfrResult = unaryOperationTwoOutputs(op, input, mpfrIntResult);
349
350 if (mpfrIntResult != libcResult.i) {
351 OS << "MPFR integral result: " << mpfrIntResult << '\n'
352 << "Libc integral result: " << libcResult.i << '\n';
353 } else {
354 OS << "Integral result from libc matches integral result from MPFR.\n";
355 }
356
357 MPFRNumber mpfrMatchValue(libcResult.f);
358 OS << "Libc floating point result is not within tolerance value of the MPFR "
359 << "result.\n\n";
360
361 OS << " Input decimal: " << mpfrInput.str() << "\n\n";
362
363 OS << "Libc floating point value: " << mpfrMatchValue.str() << '\n';
364 __llvm_libc::fputil::testing::describeValue(
365 " Libc floating point bits: ", libcResult.f, OS);
366 OS << "\n\n";
367
368 OS << " MPFR result: " << mpfrResult.str() << '\n';
369 __llvm_libc::fputil::testing::describeValue(
370 " MPFR rounded: ", mpfrResult.as<T>(), OS);
371 OS << '\n'
372 << " ULP error: "
373 << std::to_string(mpfrResult.ulp(libcResult.f)) << '\n';
374 }
375
376 template void explainUnaryOperationTwoOutputsError<float>(
377 Operation, float, const BinaryOutput<float> &, testutils::StreamWrapper &);
378 template void
379 explainUnaryOperationTwoOutputsError<double>(Operation, double,
380 const BinaryOutput<double> &,
381 testutils::StreamWrapper &);
382 template void explainUnaryOperationTwoOutputsError<long double>(
383 Operation, long double, const BinaryOutput<long double> &,
384 testutils::StreamWrapper &);
385
386 template <typename T>
explainBinaryOperationTwoOutputsError(Operation op,const BinaryInput<T> & input,const BinaryOutput<T> & libcResult,testutils::StreamWrapper & OS)387 void explainBinaryOperationTwoOutputsError(Operation op,
388 const BinaryInput<T> &input,
389 const BinaryOutput<T> &libcResult,
390 testutils::StreamWrapper &OS) {
391 MPFRNumber mpfrX(input.x);
392 MPFRNumber mpfrY(input.y);
393 FPBits<T> xbits(input.x);
394 FPBits<T> ybits(input.y);
395 int mpfrIntResult;
396 MPFRNumber mpfrResult =
397 binaryOperationTwoOutputs(op, input.x, input.y, mpfrIntResult);
398 MPFRNumber mpfrMatchValue(libcResult.f);
399
400 OS << "Input decimal: x: " << mpfrX.str() << " y: " << mpfrY.str() << '\n'
401 << "MPFR integral result: " << mpfrIntResult << '\n'
402 << "Libc integral result: " << libcResult.i << '\n'
403 << "Libc floating point result: " << mpfrMatchValue.str() << '\n'
404 << " MPFR result: " << mpfrResult.str() << '\n';
405 __llvm_libc::fputil::testing::describeValue(
406 "Libc floating point result bits: ", libcResult.f, OS);
407 __llvm_libc::fputil::testing::describeValue(
408 " MPFR rounded bits: ", mpfrResult.as<T>(), OS);
409 OS << "ULP error: " << std::to_string(mpfrResult.ulp(libcResult.f)) << '\n';
410 }
411
412 template void explainBinaryOperationTwoOutputsError<float>(
413 Operation, const BinaryInput<float> &, const BinaryOutput<float> &,
414 testutils::StreamWrapper &);
415 template void explainBinaryOperationTwoOutputsError<double>(
416 Operation, const BinaryInput<double> &, const BinaryOutput<double> &,
417 testutils::StreamWrapper &);
418 template void explainBinaryOperationTwoOutputsError<long double>(
419 Operation, const BinaryInput<long double> &,
420 const BinaryOutput<long double> &, testutils::StreamWrapper &);
421
422 template <typename T>
explainBinaryOperationOneOutputError(Operation op,const BinaryInput<T> & input,T libcResult,testutils::StreamWrapper & OS)423 void explainBinaryOperationOneOutputError(Operation op,
424 const BinaryInput<T> &input,
425 T libcResult,
426 testutils::StreamWrapper &OS) {
427 MPFRNumber mpfrX(input.x);
428 MPFRNumber mpfrY(input.y);
429 FPBits<T> xbits(input.x);
430 FPBits<T> ybits(input.y);
431 MPFRNumber mpfrResult = binaryOperationOneOutput(op, input.x, input.y);
432 MPFRNumber mpfrMatchValue(libcResult);
433
434 OS << "Input decimal: x: " << mpfrX.str() << " y: " << mpfrY.str() << '\n';
435 __llvm_libc::fputil::testing::describeValue("First input bits: ", input.x,
436 OS);
437 __llvm_libc::fputil::testing::describeValue("Second input bits: ", input.y,
438 OS);
439
440 OS << "Libc result: " << mpfrMatchValue.str() << '\n'
441 << "MPFR result: " << mpfrResult.str() << '\n';
442 __llvm_libc::fputil::testing::describeValue(
443 "Libc floating point result bits: ", libcResult, OS);
444 __llvm_libc::fputil::testing::describeValue(
445 " MPFR rounded bits: ", mpfrResult.as<T>(), OS);
446 OS << "ULP error: " << std::to_string(mpfrResult.ulp(libcResult)) << '\n';
447 }
448
449 template void explainBinaryOperationOneOutputError<float>(
450 Operation, const BinaryInput<float> &, float, testutils::StreamWrapper &);
451 template void explainBinaryOperationOneOutputError<double>(
452 Operation, const BinaryInput<double> &, double, testutils::StreamWrapper &);
453 template void explainBinaryOperationOneOutputError<long double>(
454 Operation, const BinaryInput<long double> &, long double,
455 testutils::StreamWrapper &);
456
457 template <typename T>
compareUnaryOperationSingleOutput(Operation op,T input,T libcResult,double ulpError)458 bool compareUnaryOperationSingleOutput(Operation op, T input, T libcResult,
459 double ulpError) {
460 // If the ulp error is exactly 0.5 (i.e a tie), we would check that the result
461 // is rounded to the nearest even.
462 MPFRNumber mpfrResult = unaryOperation(op, input);
463 double ulp = mpfrResult.ulp(libcResult);
464 bool bitsAreEven = ((FPBits<T>(libcResult).bitsAsUInt() & 1) == 0);
465 return (ulp < ulpError) ||
466 ((ulp == ulpError) && ((ulp != 0.5) || bitsAreEven));
467 }
468
469 template bool compareUnaryOperationSingleOutput<float>(Operation, float, float,
470 double);
471 template bool compareUnaryOperationSingleOutput<double>(Operation, double,
472 double, double);
473 template bool compareUnaryOperationSingleOutput<long double>(Operation,
474 long double,
475 long double,
476 double);
477
478 template <typename T>
compareUnaryOperationTwoOutputs(Operation op,T input,const BinaryOutput<T> & libcResult,double ulpError)479 bool compareUnaryOperationTwoOutputs(Operation op, T input,
480 const BinaryOutput<T> &libcResult,
481 double ulpError) {
482 int mpfrIntResult;
483 MPFRNumber mpfrResult = unaryOperationTwoOutputs(op, input, mpfrIntResult);
484 double ulp = mpfrResult.ulp(libcResult.f);
485
486 if (mpfrIntResult != libcResult.i)
487 return false;
488
489 bool bitsAreEven = ((FPBits<T>(libcResult.f).bitsAsUInt() & 1) == 0);
490 return (ulp < ulpError) ||
491 ((ulp == ulpError) && ((ulp != 0.5) || bitsAreEven));
492 }
493
494 template bool
495 compareUnaryOperationTwoOutputs<float>(Operation, float,
496 const BinaryOutput<float> &, double);
497 template bool
498 compareUnaryOperationTwoOutputs<double>(Operation, double,
499 const BinaryOutput<double> &, double);
500 template bool compareUnaryOperationTwoOutputs<long double>(
501 Operation, long double, const BinaryOutput<long double> &, double);
502
503 template <typename T>
compareBinaryOperationTwoOutputs(Operation op,const BinaryInput<T> & input,const BinaryOutput<T> & libcResult,double ulpError)504 bool compareBinaryOperationTwoOutputs(Operation op, const BinaryInput<T> &input,
505 const BinaryOutput<T> &libcResult,
506 double ulpError) {
507 int mpfrIntResult;
508 MPFRNumber mpfrResult =
509 binaryOperationTwoOutputs(op, input.x, input.y, mpfrIntResult);
510 double ulp = mpfrResult.ulp(libcResult.f);
511
512 if (mpfrIntResult != libcResult.i) {
513 if (op == Operation::RemQuo) {
514 if ((0x7 & mpfrIntResult) != (0x7 & libcResult.i))
515 return false;
516 } else {
517 return false;
518 }
519 }
520
521 bool bitsAreEven = ((FPBits<T>(libcResult.f).bitsAsUInt() & 1) == 0);
522 return (ulp < ulpError) ||
523 ((ulp == ulpError) && ((ulp != 0.5) || bitsAreEven));
524 }
525
526 template bool
527 compareBinaryOperationTwoOutputs<float>(Operation, const BinaryInput<float> &,
528 const BinaryOutput<float> &, double);
529 template bool
530 compareBinaryOperationTwoOutputs<double>(Operation, const BinaryInput<double> &,
531 const BinaryOutput<double> &, double);
532 template bool compareBinaryOperationTwoOutputs<long double>(
533 Operation, const BinaryInput<long double> &,
534 const BinaryOutput<long double> &, double);
535
536 template <typename T>
compareBinaryOperationOneOutput(Operation op,const BinaryInput<T> & input,T libcResult,double ulpError)537 bool compareBinaryOperationOneOutput(Operation op, const BinaryInput<T> &input,
538 T libcResult, double ulpError) {
539 MPFRNumber mpfrResult = binaryOperationOneOutput(op, input.x, input.y);
540 double ulp = mpfrResult.ulp(libcResult);
541
542 bool bitsAreEven = ((FPBits<T>(libcResult).bitsAsUInt() & 1) == 0);
543 return (ulp < ulpError) ||
544 ((ulp == ulpError) && ((ulp != 0.5) || bitsAreEven));
545 }
546
547 template bool compareBinaryOperationOneOutput<float>(Operation,
548 const BinaryInput<float> &,
549 float, double);
550 template bool
551 compareBinaryOperationOneOutput<double>(Operation, const BinaryInput<double> &,
552 double, double);
553 template bool compareBinaryOperationOneOutput<long double>(
554 Operation, const BinaryInput<long double> &, long double, double);
555
556 } // namespace internal
557
558 } // namespace mpfr
559 } // namespace testing
560 } // namespace __llvm_libc
561