blob: 4936e7738a663ee93c2efabe053ed4a107347056 [file] [log] [blame]
//===-- 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<TypeCategory::Real, 16>
// 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/float128.h"
#include <cstdint>
#include <limits>
namespace Fortran::runtime {
// MAX/MIN/LOWEST values for different data types.
// MaxOrMinIdentity returns MAX or LOWEST value of the given type.
template <TypeCategory CAT, int KIND, bool IS_MAXVAL, typename Enable = void>
struct MaxOrMinIdentity {
using Type = CppTypeFor<CAT, KIND>;
static constexpr RT_API_ATTRS Type Value() {
return IS_MAXVAL ? std::numeric_limits<Type>::lowest()
: std::numeric_limits<Type>::max();
}
};
// std::numeric_limits<> may not know int128_t
template <bool IS_MAXVAL>
struct MaxOrMinIdentity<TypeCategory::Integer, 16, IS_MAXVAL> {
using Type = CppTypeFor<TypeCategory::Integer, 16>;
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<TypeCategory::Real, 16> is __float128.
template <bool IS_MAXVAL>
struct MaxOrMinIdentity<TypeCategory::Real, 16, IS_MAXVAL,
typename std::enable_if_t<
std::is_same_v<CppTypeFor<TypeCategory::Real, 16>, __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<Type *>(data) = Type(1.0);
if (*reinterpret_cast<std::uint64_t *>(data) != 0 ||
*(reinterpret_cast<std::uint64_t *>(data) + 1) != 0x3FFF000000000000) {
Terminator terminator{__FILE__, __LINE__};
terminator.Crash("not yet implemented: no full support for __float128");
}
// Recreate FLT128_MAX.
*reinterpret_cast<std::uint64_t *>(data) = 0xFFFFFFFFFFFFFFFF;
*(reinterpret_cast<std::uint64_t *>(data) + 1) = 0x7FFEFFFFFFFFFFFF;
Type max = *reinterpret_cast<Type *>(data);
return IS_MAXVAL ? -max : max;
}
};
#endif // HAS_FLOAT128
// Minimum finite representable value.
// For floating-point types, returns minimum positive normalized value.
template <typename T> struct MinValue {
static RT_API_ATTRS T get() { return std::numeric_limits<T>::min(); }
};
#if HAS_FLOAT128
template <> struct MinValue<CppTypeFor<TypeCategory::Real, 16>> {
using Type = CppTypeFor<TypeCategory::Real, 16>;
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<Type *>(data) = Type(1.0);
if (*reinterpret_cast<std::uint64_t *>(data) != 0 ||
*(reinterpret_cast<std::uint64_t *>(data) + 1) != 0x3FFF000000000000) {
Terminator terminator{__FILE__, __LINE__};
terminator.Crash("not yet implemented: no full support for __float128");
}
// Recreate FLT128_MIN.
*reinterpret_cast<std::uint64_t *>(data) = 0;
*(reinterpret_cast<std::uint64_t *>(data) + 1) = 0x1000000000000;
return *reinterpret_cast<Type *>(data);
}
};
#endif // HAS_FLOAT128
template <typename T> 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 <typename T> struct FREXPTy {
static constexpr RT_API_ATTRS T compute(T x, int *e) {
return std::frexp(x, e);
}
};
RT_DIAG_POP
template <typename T> struct ILOGBTy {
static constexpr RT_API_ATTRS int compute(T x) { return std::ilogb(x); }
};
template <typename T> struct ISINFTy {
static constexpr RT_API_ATTRS bool compute(T x) { return std::isinf(x); }
};
template <typename T> struct ISNANTy {
static constexpr RT_API_ATTRS bool compute(T x) { return std::isnan(x); }
};
template <typename T> struct LDEXPTy {
template <typename ET> static constexpr RT_API_ATTRS T compute(T x, ET e) {
return std::ldexp(x, e);
}
};
template <typename T> struct MAXTy {
static constexpr RT_API_ATTRS T compute() {
return std::numeric_limits<T>::max();
}
};
#if LDBL_MANT_DIG == 113 || HAS_FLOAT128
template <> struct MAXTy<CppTypeFor<TypeCategory::Real, 16>> {
static CppTypeFor<TypeCategory::Real, 16> compute() {
return MaxOrMinIdentity<TypeCategory::Real, 16, true>::Value();
}
};
#endif
template <typename T> struct MINTy {
static constexpr RT_API_ATTRS T compute() { return MinValue<T>::get(); }
};
template <typename T> struct QNANTy {
static constexpr RT_API_ATTRS T compute() {
return std::numeric_limits<T>::quiet_NaN();
}
};
template <typename T> struct SQRTTy {
static constexpr RT_API_ATTRS T compute(T x) { return std::sqrt(x); }
};
// EXPONENT (16.9.75)
template <typename RESULT, typename ARG>
inline RT_API_ATTRS RESULT Exponent(ARG x) {
if (ISINFTy<ARG>::compute(x) || ISNANTy<ARG>::compute(x)) {
return MAXTy<RESULT>::compute(); // +/-Inf, NaN -> HUGE(0)
} else if (x == 0) {
return 0; // 0 -> 0
} else {
return ILOGBTy<ARG>::compute(x) + 1;
}
}
// FRACTION (16.9.80)
template <typename T> inline RT_API_ATTRS T Fraction(T x) {
if (ISNANTy<T>::compute(x)) {
return x; // NaN -> same NaN
} else if (ISINFTy<T>::compute(x)) {
return QNANTy<T>::compute(); // +/-Inf -> NaN
} else if (x == 0) {
return x; // 0 -> same 0
} else {
int ignoredExp;
return FREXPTy<T>::compute(x, &ignoredExp);
}
}
// SET_EXPONENT (16.9.171)
template <typename T> inline RT_API_ATTRS T SetExponent(T x, std::int64_t p) {
if (ISNANTy<T>::compute(x)) {
return x; // NaN -> same NaN
} else if (ISINFTy<T>::compute(x)) {
return QNANTy<T>::compute(); // +/-Inf -> NaN
} else if (x == 0) {
return x; // return negative zero if x is negative zero
} else {
int expo{ILOGBTy<T>::compute(x) + 1};
auto ip{static_cast<int>(p - expo)};
if (ip != p - expo) {
ip = p < 0 ? std::numeric_limits<int>::min()
: std::numeric_limits<int>::max();
}
return LDEXPTy<T>::compute(x, ip); // x*2**(p-e)
}
}
// MOD & MODULO (16.9.135, .136)
template <bool IS_MODULO, typename T>
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<T>::compute(a) || ISNANTy<T>::compute(p) ||
ISINFTy<T>::compute(a)) {
return QNANTy<T>::compute();
} else if (IS_MODULO && ISINFTy<T>::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<T>::compute();
}
T aAbs{ABSTy<T>::compute(a)};
T pAbs{ABSTy<T>::compute(p)};
if (aAbs <= static_cast<T>(std::numeric_limits<std::int64_t>::max()) &&
pAbs <= static_cast<T>(std::numeric_limits<std::int64_t>::max())) {
if (auto aInt{static_cast<std::int64_t>(a)}; a == aInt) {
if (auto pInt{static_cast<std::int64_t>(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<T>(mod);
}
}
}
if constexpr (std::is_same_v<T, float> || std::is_same_v<T, double> ||
std::is_same_v<T, long double>) {
// 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<int>(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 <int PREC, typename T> inline RT_API_ATTRS T RRSpacing(T x) {
if (ISNANTy<T>::compute(x)) {
return x; // NaN -> same NaN
} else if (ISINFTy<T>::compute(x)) {
return QNANTy<T>::compute(); // +/-Inf -> NaN
} else if (x == 0) {
return 0; // 0 -> 0
} else {
return LDEXPTy<T>::compute(
ABSTy<T>::compute(x), PREC - (ILOGBTy<T>::compute(x) + 1));
}
}
// SPACING (16.9.180)
template <int PREC, typename T> inline RT_API_ATTRS T Spacing(T x) {
if (ISNANTy<T>::compute(x)) {
return x; // NaN -> same NaN
} else if (ISINFTy<T>::compute(x)) {
return QNANTy<T>::compute(); // +/-Inf -> NaN
} else if (x == 0) {
// The standard-mandated behavior seems broken, since TINY() can't be
// subnormal.
return MINTy<T>::compute(); // 0 -> TINY(x)
} else {
T result{LDEXPTy<T>::compute(
static_cast<T>(1.0), ILOGBTy<T>::compute(x) + 1 - PREC)}; // 2**(e-p)
return result == 0 ? /*TINY(x)*/ MINTy<T>::compute() : result;
}
}
} // namespace Fortran::runtime
#endif // FORTRAN_RUNTIME_NUMERIC_TEMPLATES_H_