//===-- MPCommon.h ----------------------------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #ifndef LLVM_LIBC_UTILS_MPFRWRAPPER_MPCOMMON_H #define LLVM_LIBC_UTILS_MPFRWRAPPER_MPCOMMON_H #include "hdr/stdint_proxy.h" #include "src/__support/CPP/string.h" #include "src/__support/CPP/type_traits.h" #include "src/__support/FPUtil/FPBits.h" #include "src/__support/macros/config.h" #include "src/__support/macros/properties/types.h" #include "test/UnitTest/RoundingModeUtils.h" #include "mpfr_inc.h" #ifdef LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE extern "C" { int mpfr_set_float128(mpfr_ptr, float128, mpfr_rnd_t); float128 mpfr_get_float128(mpfr_srcptr, mpfr_rnd_t); } #endif namespace LIBC_NAMESPACE_DECL { namespace testing { namespace mpfr { template using FPBits = LIBC_NAMESPACE::fputil::FPBits; using LIBC_NAMESPACE::fputil::testing::RoundingMode; // A precision value which allows sufficiently large additional // precision compared to the floating point precision. template struct ExtraPrecision; #ifdef LIBC_TYPES_HAS_FLOAT16 template <> struct ExtraPrecision { static constexpr unsigned int VALUE = 128; }; #endif template <> struct ExtraPrecision { static constexpr unsigned int VALUE = 128; }; template <> struct ExtraPrecision { static constexpr unsigned int VALUE = 256; }; template <> struct ExtraPrecision { #ifdef LIBC_TYPES_LONG_DOUBLE_IS_FLOAT128 static constexpr unsigned int VALUE = 512; #else static constexpr unsigned int VALUE = 256; #endif }; #if defined(LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE) template <> struct ExtraPrecision { static constexpr unsigned int VALUE = 512; }; #endif // LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE template <> struct ExtraPrecision { static constexpr unsigned int VALUE = 64; }; // If the ulp tolerance is less than or equal to 0.5, we would check that the // result is rounded correctly with respect to the rounding mode by using the // same precision as the inputs. template static inline unsigned int get_precision(double ulp_tolerance) { if (ulp_tolerance <= 0.5) { return LIBC_NAMESPACE::fputil::FPBits::FRACTION_LEN + 1; } else { return ExtraPrecision::VALUE; } } static inline mpfr_rnd_t get_mpfr_rounding_mode(RoundingMode mode) { switch (mode) { case RoundingMode::Upward: return MPFR_RNDU; break; case RoundingMode::Downward: return MPFR_RNDD; break; case RoundingMode::TowardZero: return MPFR_RNDZ; break; case RoundingMode::Nearest: return MPFR_RNDN; break; } __builtin_unreachable(); } class MPFRNumber { unsigned int mpfr_precision; mpfr_rnd_t mpfr_rounding; mpfr_t value; public: MPFRNumber(); // We use explicit EnableIf specializations to disallow implicit // conversions. Implicit conversions can potentially lead to loss of // precision. We exceptionally allow implicit conversions from float16 // to float, as the MPFR API does not support float16, thus requiring // conversion to a higher-precision format. template #ifdef LIBC_TYPES_HAS_FLOAT16 || cpp::is_same_v #endif || cpp::is_same_v, int> = 0> explicit MPFRNumber(XType x, unsigned int precision = ExtraPrecision::VALUE, RoundingMode rounding = RoundingMode::Nearest) : mpfr_precision(precision), mpfr_rounding(get_mpfr_rounding_mode(rounding)) { mpfr_init2(value, mpfr_precision); mpfr_set_flt(value, x, mpfr_rounding); } template , int> = 0> explicit MPFRNumber(XType x, unsigned int precision = ExtraPrecision::VALUE, RoundingMode rounding = RoundingMode::Nearest) : mpfr_precision(precision), mpfr_rounding(get_mpfr_rounding_mode(rounding)) { mpfr_init2(value, mpfr_precision); mpfr_set_d(value, x, mpfr_rounding); } template , int> = 0> explicit MPFRNumber(XType x, unsigned int precision = ExtraPrecision::VALUE, RoundingMode rounding = RoundingMode::Nearest) : mpfr_precision(precision), mpfr_rounding(get_mpfr_rounding_mode(rounding)) { mpfr_init2(value, mpfr_precision); mpfr_set_ld(value, x, mpfr_rounding); } #ifdef LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE template , int> = 0> explicit MPFRNumber(XType x, unsigned int precision = ExtraPrecision::VALUE, RoundingMode rounding = RoundingMode::Nearest) : mpfr_precision(precision), mpfr_rounding(get_mpfr_rounding_mode(rounding)) { mpfr_init2(value, mpfr_precision); mpfr_set_float128(value, x, mpfr_rounding); } #endif // LIBC_TYPES_FLOAT128_IS_NOT_LONG_DOUBLE template , int> = 0> explicit MPFRNumber(XType x, unsigned int precision = ExtraPrecision::VALUE, RoundingMode rounding = RoundingMode::Nearest) : mpfr_precision(precision), mpfr_rounding(get_mpfr_rounding_mode(rounding)) { mpfr_init2(value, mpfr_precision); mpfr_set_sj(value, x, mpfr_rounding); } MPFRNumber(const MPFRNumber &other); MPFRNumber(const MPFRNumber &other, unsigned int precision); MPFRNumber(const mpfr_t x, unsigned int precision, RoundingMode rounding); ~MPFRNumber(); MPFRNumber &operator=(const MPFRNumber &rhs); bool is_nan() const; MPFRNumber abs() const; MPFRNumber acos() const; MPFRNumber acosh() const; MPFRNumber acospi() const; MPFRNumber add(const MPFRNumber &b) const; MPFRNumber asin() const; MPFRNumber asinh() const; MPFRNumber atan() const; MPFRNumber atan2(const MPFRNumber &b); MPFRNumber atanh() const; MPFRNumber cbrt() const; MPFRNumber ceil() const; MPFRNumber cos() const; MPFRNumber cosh() const; MPFRNumber cospi() const; MPFRNumber erf() const; MPFRNumber exp() const; MPFRNumber exp2() const; MPFRNumber exp2m1() const; MPFRNumber exp10() const; MPFRNumber exp10m1() const; MPFRNumber expm1() const; MPFRNumber div(const MPFRNumber &b) const; MPFRNumber floor() const; MPFRNumber fmod(const MPFRNumber &b); MPFRNumber frexp(int &exp); MPFRNumber hypot(const MPFRNumber &b); MPFRNumber log() const; MPFRNumber log2() const; MPFRNumber log10() const; MPFRNumber log1p() const; MPFRNumber pow(const MPFRNumber &b); MPFRNumber remquo(const MPFRNumber &divisor, int "ient); MPFRNumber round() const; MPFRNumber roundeven() const; bool round_to_long(long &result) const; bool round_to_long(mpfr_rnd_t rnd, long &result) const; MPFRNumber rint(mpfr_rnd_t rnd) const; MPFRNumber mod_2pi() const; MPFRNumber mod_pi_over_2() const; MPFRNumber mod_pi_over_4() const; MPFRNumber sin() const; MPFRNumber sinpi() const; MPFRNumber sinh() const; MPFRNumber sqrt() const; MPFRNumber sub(const MPFRNumber &b) const; MPFRNumber tan() const; MPFRNumber tanh() const; MPFRNumber tanpi() const; MPFRNumber trunc() const; MPFRNumber fma(const MPFRNumber &b, const MPFRNumber &c); MPFRNumber mul(const MPFRNumber &b); cpp::string str() const; template T as() const; void dump(const char *msg) const; // Return the ULP (units-in-the-last-place) difference between the // stored MPFR and a floating point number. // // We define ULP difference as follows: // If exponents of this value and the |input| are same, then: // ULP(this_value, input) = abs(this_value - input) / eps(input) // else: // max = max(abs(this_value), abs(input)) // min = min(abs(this_value), abs(input)) // maxExponent = exponent(max) // ULP(this_value, input) = (max - 2^maxExponent) / eps(max) + // (2^maxExponent - min) / eps(min) // // Remarks: // 1. A ULP of 0.0 will imply that the value is correctly rounded. // 2. We expect that this value and the value to be compared (the [input] // argument) are reasonable close, and we will provide an upper bound // of ULP value for testing. Morever, most of the fractional parts of // ULP value do not matter much, so using double as the return type // should be good enough. // 3. For close enough values (values which don't diff in their exponent by // not more than 1), a ULP difference of N indicates a bit distance // of N between this number and [input]. // 4. A values of +0.0 and -0.0 are treated as equal. template cpp::enable_if_t, MPFRNumber> ulp_as_mpfr_number(T input) { T thisAsT = as(); if (thisAsT == input) return MPFRNumber(0.0); if (is_nan()) { if (FPBits(input).is_nan()) return MPFRNumber(0.0); return MPFRNumber(FPBits::inf().get_val()); } int thisExponent = FPBits(thisAsT).get_exponent(); int inputExponent = FPBits(input).get_exponent(); // Adjust the exponents for denormal numbers. if (FPBits(thisAsT).is_subnormal()) ++thisExponent; if (FPBits(input).is_subnormal()) ++inputExponent; if (thisAsT * input < 0 || thisExponent == inputExponent) { MPFRNumber inputMPFR(input); mpfr_sub(inputMPFR.value, value, inputMPFR.value, MPFR_RNDN); mpfr_abs(inputMPFR.value, inputMPFR.value, MPFR_RNDN); mpfr_mul_2si(inputMPFR.value, inputMPFR.value, -thisExponent + FPBits::FRACTION_LEN, MPFR_RNDN); return inputMPFR; } // If the control reaches here, it means that this number and input are // of the same sign but different exponent. In such a case, ULP error is // calculated as sum of two parts. thisAsT = FPBits(thisAsT).abs().get_val(); input = FPBits(input).abs().get_val(); T min = thisAsT > input ? input : thisAsT; T max = thisAsT > input ? thisAsT : input; int minExponent = FPBits(min).get_exponent(); int maxExponent = FPBits(max).get_exponent(); // Adjust the exponents for denormal numbers. if (FPBits(min).is_subnormal()) ++minExponent; if (FPBits(max).is_subnormal()) ++maxExponent; MPFRNumber minMPFR(min); MPFRNumber maxMPFR(max); MPFRNumber pivot(uint32_t(1)); mpfr_mul_2si(pivot.value, pivot.value, maxExponent, MPFR_RNDN); mpfr_sub(minMPFR.value, pivot.value, minMPFR.value, MPFR_RNDN); mpfr_mul_2si(minMPFR.value, minMPFR.value, -minExponent + FPBits::FRACTION_LEN, MPFR_RNDN); mpfr_sub(maxMPFR.value, maxMPFR.value, pivot.value, MPFR_RNDN); mpfr_mul_2si(maxMPFR.value, maxMPFR.value, -maxExponent + FPBits::FRACTION_LEN, MPFR_RNDN); mpfr_add(minMPFR.value, minMPFR.value, maxMPFR.value, MPFR_RNDN); return minMPFR; } template cpp::enable_if_t, cpp::string> ulp_as_string(T input) { MPFRNumber num = ulp_as_mpfr_number(input); return num.str(); } template cpp::enable_if_t, double> ulp(T input) { MPFRNumber num = ulp_as_mpfr_number(input); return num.as(); } }; } // namespace mpfr } // namespace testing } // namespace LIBC_NAMESPACE_DECL #endif // LLVM_LIBC_UTILS_MPFRWRAPPER_MPCOMMON_H