//===-- runtime/numeric-templates.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 // //===----------------------------------------------------------------------===// // Generic class and function templates used for implementing // various numeric intrinsics (EXPONENT, FRACTION, etc.). // // This header file also defines generic templates for "basic" // math operations like abs, isnan, etc. The Float128Math // library provides specializations for these templates // for the data type corresponding to CppTypeFor // on the target. #ifndef FORTRAN_RUNTIME_NUMERIC_TEMPLATES_H_ #define FORTRAN_RUNTIME_NUMERIC_TEMPLATES_H_ #include "terminator.h" #include "tools.h" #include "flang/Common/api-attrs.h" #include "flang/Common/erfc-scaled.h" #include "flang/Common/float128.h" #include #include namespace Fortran::runtime { // MAX/MIN/LOWEST values for different data types. // MaxOrMinIdentity returns MAX or LOWEST value of the given type. template struct MaxOrMinIdentity { using Type = CppTypeFor; static constexpr RT_API_ATTRS Type Value() { return IS_MAXVAL ? std::numeric_limits::lowest() : std::numeric_limits::max(); } }; // std::numeric_limits<> may not know int128_t template struct MaxOrMinIdentity { using Type = CppTypeFor; static constexpr RT_API_ATTRS Type Value() { return IS_MAXVAL ? Type{1} << 127 : ~Type{0} >> 1; } }; #if HAS_FLOAT128 // std::numeric_limits<> may not support __float128. // // Usage of GCC quadmath.h's FLT128_MAX is complicated by the fact that // even GCC complains about 'Q' literal suffix under -Wpedantic. // We just recreate FLT128_MAX ourselves. // // This specialization must engage only when // CppTypeFor is __float128. template struct MaxOrMinIdentity, __float128>>> { using Type = __float128; static RT_API_ATTRS Type Value() { // Create a buffer to store binary representation of __float128 constant. constexpr std::size_t alignment = std::max(alignof(Type), alignof(std::uint64_t)); alignas(alignment) char data[sizeof(Type)]; // First, verify that our interpretation of __float128 format is correct, // e.g. by checking at least one known constant. *reinterpret_cast(data) = Type(1.0); if (*reinterpret_cast(data) != 0 || *(reinterpret_cast(data) + 1) != 0x3FFF000000000000) { Terminator terminator{__FILE__, __LINE__}; terminator.Crash("not yet implemented: no full support for __float128"); } // Recreate FLT128_MAX. *reinterpret_cast(data) = 0xFFFFFFFFFFFFFFFF; *(reinterpret_cast(data) + 1) = 0x7FFEFFFFFFFFFFFF; Type max = *reinterpret_cast(data); return IS_MAXVAL ? -max : max; } }; #endif // HAS_FLOAT128 // Minimum finite representable value. // For floating-point types, returns minimum positive normalized value. template struct MinValue { static RT_API_ATTRS T get() { return std::numeric_limits::min(); } }; template struct MinValue<11, T> { // TINY(0._2) static constexpr RT_API_ATTRS T get() { return 0.00006103515625E-04; } }; #if HAS_FLOAT128 template <> struct MinValue<113, CppTypeFor> { using Type = CppTypeFor; static RT_API_ATTRS Type get() { // Create a buffer to store binary representation of __float128 constant. constexpr std::size_t alignment = std::max(alignof(Type), alignof(std::uint64_t)); alignas(alignment) char data[sizeof(Type)]; // First, verify that our interpretation of __float128 format is correct, // e.g. by checking at least one known constant. *reinterpret_cast(data) = Type(1.0); if (*reinterpret_cast(data) != 0 || *(reinterpret_cast(data) + 1) != 0x3FFF000000000000) { Terminator terminator{__FILE__, __LINE__}; terminator.Crash("not yet implemented: no full support for __float128"); } // Recreate FLT128_MIN. *reinterpret_cast(data) = 0; *(reinterpret_cast(data) + 1) = 0x1000000000000; return *reinterpret_cast(data); } }; #endif // HAS_FLOAT128 template struct ABSTy { static constexpr RT_API_ATTRS T compute(T x) { return std::abs(x); } }; // Suppress the warnings about calling __host__-only // 'long double' std::frexp, from __device__ code. RT_DIAG_PUSH RT_DIAG_DISABLE_CALL_HOST_FROM_DEVICE_WARN template struct FREXPTy { static constexpr RT_API_ATTRS T compute(T x, int *e) { return std::frexp(x, e); } }; RT_DIAG_POP template struct ILOGBTy { static constexpr RT_API_ATTRS int compute(T x) { return std::ilogb(x); } }; template struct ISINFTy { static constexpr RT_API_ATTRS bool compute(T x) { return std::isinf(x); } }; template struct ISNANTy { static constexpr RT_API_ATTRS bool compute(T x) { return std::isnan(x); } }; template struct LDEXPTy { template static constexpr RT_API_ATTRS T compute(T x, ET e) { return std::ldexp(x, e); } }; template struct MAXTy { static constexpr RT_API_ATTRS T compute() { return std::numeric_limits::max(); } }; #if HAS_LDBL128 || HAS_FLOAT128 template <> struct MAXTy> { static CppTypeFor compute() { return MaxOrMinIdentity::Value(); } }; #endif template struct MINTy { static constexpr RT_API_ATTRS T compute() { return MinValue::get(); } }; template struct QNANTy { static constexpr RT_API_ATTRS T compute() { return std::numeric_limits::quiet_NaN(); } }; template struct SQRTTy { static constexpr RT_API_ATTRS T compute(T x) { return std::sqrt(x); } }; // EXPONENT (16.9.75) template inline RT_API_ATTRS RESULT Exponent(ARG x) { if (ISINFTy::compute(x) || ISNANTy::compute(x)) { return MAXTy::compute(); // +/-Inf, NaN -> HUGE(0) } else if (x == 0) { return 0; // 0 -> 0 } else { return ILOGBTy::compute(x) + 1; } } // FRACTION (16.9.80) template inline RT_API_ATTRS T Fraction(T x) { if (ISNANTy::compute(x)) { return x; // NaN -> same NaN } else if (ISINFTy::compute(x)) { return QNANTy::compute(); // +/-Inf -> NaN } else if (x == 0) { return x; // 0 -> same 0 } else { int ignoredExp; return FREXPTy::compute(x, &ignoredExp); } } // SET_EXPONENT (16.9.171) template inline RT_API_ATTRS T SetExponent(T x, std::int64_t p) { if (ISNANTy::compute(x)) { return x; // NaN -> same NaN } else if (ISINFTy::compute(x)) { return QNANTy::compute(); // +/-Inf -> NaN } else if (x == 0) { return x; // return negative zero if x is negative zero } else { int expo{ILOGBTy::compute(x) + 1}; auto ip{static_cast(p - expo)}; if (ip != p - expo) { ip = p < 0 ? std::numeric_limits::min() : std::numeric_limits::max(); } return LDEXPTy::compute(x, ip); // x*2**(p-e) } } // MOD & MODULO (16.9.135, .136) template inline RT_API_ATTRS T RealMod( T a, T p, const char *sourceFile, int sourceLine) { if (p == 0) { Terminator{sourceFile, sourceLine}.Crash( IS_MODULO ? "MODULO with P==0" : "MOD with P==0"); } if (ISNANTy::compute(a) || ISNANTy::compute(p) || ISINFTy::compute(a)) { return QNANTy::compute(); } else if (IS_MODULO && ISINFTy::compute(p)) { // Other compilers behave consistently for MOD(x, +/-INF) // and always return x. This is probably related to // implementation of std::fmod(). Stick to this behavior // for MOD, but return NaN for MODULO(x, +/-INF). return QNANTy::compute(); } T aAbs{ABSTy::compute(a)}; T pAbs{ABSTy::compute(p)}; if (aAbs <= static_cast(std::numeric_limits::max()) && pAbs <= static_cast(std::numeric_limits::max())) { if (auto aInt{static_cast(a)}; a == aInt) { if (auto pInt{static_cast(p)}; p == pInt) { // Fast exact case for integer operands auto mod{aInt - (aInt / pInt) * pInt}; if constexpr (IS_MODULO) { if (mod == 0) { // Return properly signed zero. return pInt > 0 ? T{0} : -T{0}; } if ((aInt > 0) != (pInt > 0)) { mod += pInt; } } else { if (mod == 0) { // Return properly signed zero. return aInt > 0 ? T{0} : -T{0}; } } return static_cast(mod); } } } if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { // std::fmod() semantics on signed operands seems to match // the requirements of MOD(). MODULO() needs adjustment. T result{std::fmod(a, p)}; if constexpr (IS_MODULO) { if ((a < 0) != (p < 0)) { if (result == 0.) { result = -result; } else { result += p; } } } return result; } else { // The standard defines MOD(a,p)=a-AINT(a/p)*p and // MODULO(a,p)=a-FLOOR(a/p)*p, but those definitions lose // precision badly due to cancellation when ABS(a) is // much larger than ABS(p). // Insights: // - MOD(a,p)=MOD(a-n*p,p) when a>0, p>0, integer n>0, and a>=n*p // - when n is a power of two, n*p is exact // - as a>=n*p, a-n*p does not round. // So repeatedly reduce a by all n*p in decreasing order of n; // what's left is the desired remainder. This is basically // the same algorithm as arbitrary precision binary long division, // discarding the quotient. T tmp{aAbs}; for (T adj{SetExponent(pAbs, Exponent(aAbs))}; tmp >= pAbs; adj /= 2) { if (tmp >= adj) { tmp -= adj; if (tmp == 0) { break; } } } if (a < 0) { tmp = -tmp; } if constexpr (IS_MODULO) { if ((a < 0) != (p < 0)) { if (tmp == 0.) { tmp = -tmp; } else { tmp += p; } } } return tmp; } } // RRSPACING (16.9.164) template inline RT_API_ATTRS T RRSpacing(T x) { if (ISNANTy::compute(x)) { return x; // NaN -> same NaN } else if (ISINFTy::compute(x)) { return QNANTy::compute(); // +/-Inf -> NaN } else if (x == 0) { return 0; // 0 -> 0 } else { return LDEXPTy::compute( ABSTy::compute(x), PREC - (ILOGBTy::compute(x) + 1)); } } // SPACING (16.9.180) template inline RT_API_ATTRS T Spacing(T x) { T tiny{MINTy::compute()}; if (ISNANTy::compute(x)) { return x; // NaN -> same NaN } else if (ISINFTy::compute(x)) { return QNANTy::compute(); // +/-Inf -> NaN } else if (x == 0) { // 0 -> TINY(x) return tiny; } else { T result{LDEXPTy::compute( static_cast(1.0), ILOGBTy::compute(x) + 1 - PREC)}; // 2**(e-p) // All compilers return TINY(x) for |x| <= TINY(x), but differ over whether // SPACING(x) can be < TINY(x) for |x| > TINY(x). The most common precedent // is to never return a value < TINY(x). return result <= tiny ? tiny : result; } } // ERFC_SCALED (16.9.71) template inline RT_API_ATTRS T ErfcScaled(T arg) { return common::ErfcScaled(arg); } } // namespace Fortran::runtime #endif // FORTRAN_RUNTIME_NUMERIC_TEMPLATES_H_