xref: /llvm-project/flang/runtime/numeric-templates.h (revision 38b9dd7a7f393f990251c6cc204cfbea05930a0e)
1 //===-- runtime/numeric-templates.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 // Generic class and function templates used for implementing
10 // various numeric intrinsics (EXPONENT, FRACTION, etc.).
11 //
12 // This header file also defines generic templates for "basic"
13 // math operations like abs, isnan, etc. The Float128Math
14 // library provides specializations for these templates
15 // for the data type corresponding to CppTypeFor<TypeCategory::Real, 16>
16 // on the target.
17 
18 #ifndef FORTRAN_RUNTIME_NUMERIC_TEMPLATES_H_
19 #define FORTRAN_RUNTIME_NUMERIC_TEMPLATES_H_
20 
21 #include "terminator.h"
22 #include "tools.h"
23 #include "flang/Common/api-attrs.h"
24 #include "flang/Common/erfc-scaled.h"
25 #include "flang/Common/float128.h"
26 #include <cstdint>
27 #include <limits>
28 
29 namespace Fortran::runtime {
30 
31 // MAX/MIN/LOWEST values for different data types.
32 
33 // MaxOrMinIdentity returns MAX or LOWEST value of the given type.
34 template <TypeCategory CAT, int KIND, bool IS_MAXVAL, typename Enable = void>
35 struct MaxOrMinIdentity {
36   using Type = CppTypeFor<CAT, KIND>;
37   static constexpr RT_API_ATTRS Type Value() {
38     return IS_MAXVAL ? std::numeric_limits<Type>::lowest()
39                      : std::numeric_limits<Type>::max();
40   }
41 };
42 
43 // std::numeric_limits<> may not know int128_t
44 template <bool IS_MAXVAL>
45 struct MaxOrMinIdentity<TypeCategory::Integer, 16, IS_MAXVAL> {
46   using Type = CppTypeFor<TypeCategory::Integer, 16>;
47   static constexpr RT_API_ATTRS Type Value() {
48     return IS_MAXVAL ? Type{1} << 127 : ~Type{0} >> 1;
49   }
50 };
51 
52 #if HAS_FLOAT128
53 // std::numeric_limits<> may not support __float128.
54 //
55 // Usage of GCC quadmath.h's FLT128_MAX is complicated by the fact that
56 // even GCC complains about 'Q' literal suffix under -Wpedantic.
57 // We just recreate FLT128_MAX ourselves.
58 //
59 // This specialization must engage only when
60 // CppTypeFor<TypeCategory::Real, 16> is __float128.
61 template <bool IS_MAXVAL>
62 struct MaxOrMinIdentity<TypeCategory::Real, 16, IS_MAXVAL,
63     typename std::enable_if_t<
64         std::is_same_v<CppTypeFor<TypeCategory::Real, 16>, __float128>>> {
65   using Type = __float128;
66   static RT_API_ATTRS Type Value() {
67     // Create a buffer to store binary representation of __float128 constant.
68     constexpr std::size_t alignment =
69         std::max(alignof(Type), alignof(std::uint64_t));
70     alignas(alignment) char data[sizeof(Type)];
71 
72     // First, verify that our interpretation of __float128 format is correct,
73     // e.g. by checking at least one known constant.
74     *reinterpret_cast<Type *>(data) = Type(1.0);
75     if (*reinterpret_cast<std::uint64_t *>(data) != 0 ||
76         *(reinterpret_cast<std::uint64_t *>(data) + 1) != 0x3FFF000000000000) {
77       Terminator terminator{__FILE__, __LINE__};
78       terminator.Crash("not yet implemented: no full support for __float128");
79     }
80 
81     // Recreate FLT128_MAX.
82     *reinterpret_cast<std::uint64_t *>(data) = 0xFFFFFFFFFFFFFFFF;
83     *(reinterpret_cast<std::uint64_t *>(data) + 1) = 0x7FFEFFFFFFFFFFFF;
84     Type max = *reinterpret_cast<Type *>(data);
85     return IS_MAXVAL ? -max : max;
86   }
87 };
88 #endif // HAS_FLOAT128
89 
90 // Minimum finite representable value.
91 // For floating-point types, returns minimum positive normalized value.
92 template <int PREC, typename T> struct MinValue {
93   static RT_API_ATTRS T get() { return std::numeric_limits<T>::min(); }
94 };
95 template <typename T> struct MinValue<11, T> {
96   // TINY(0._2)
97   static constexpr RT_API_ATTRS T get() { return 0.00006103515625E-04; }
98 };
99 
100 #if HAS_FLOAT128
101 template <> struct MinValue<113, CppTypeFor<TypeCategory::Real, 16>> {
102   using Type = CppTypeFor<TypeCategory::Real, 16>;
103   static RT_API_ATTRS Type get() {
104     // Create a buffer to store binary representation of __float128 constant.
105     constexpr std::size_t alignment =
106         std::max(alignof(Type), alignof(std::uint64_t));
107     alignas(alignment) char data[sizeof(Type)];
108 
109     // First, verify that our interpretation of __float128 format is correct,
110     // e.g. by checking at least one known constant.
111     *reinterpret_cast<Type *>(data) = Type(1.0);
112     if (*reinterpret_cast<std::uint64_t *>(data) != 0 ||
113         *(reinterpret_cast<std::uint64_t *>(data) + 1) != 0x3FFF000000000000) {
114       Terminator terminator{__FILE__, __LINE__};
115       terminator.Crash("not yet implemented: no full support for __float128");
116     }
117 
118     // Recreate FLT128_MIN.
119     *reinterpret_cast<std::uint64_t *>(data) = 0;
120     *(reinterpret_cast<std::uint64_t *>(data) + 1) = 0x1000000000000;
121     return *reinterpret_cast<Type *>(data);
122   }
123 };
124 #endif // HAS_FLOAT128
125 
126 template <typename T> struct ABSTy {
127   static constexpr RT_API_ATTRS T compute(T x) { return std::abs(x); }
128 };
129 
130 // Suppress the warnings about calling __host__-only
131 // 'long double' std::frexp, from __device__ code.
132 RT_DIAG_PUSH
133 RT_DIAG_DISABLE_CALL_HOST_FROM_DEVICE_WARN
134 
135 template <typename T> struct FREXPTy {
136   static constexpr RT_API_ATTRS T compute(T x, int *e) {
137     return std::frexp(x, e);
138   }
139 };
140 
141 RT_DIAG_POP
142 
143 template <typename T> struct ILOGBTy {
144   static constexpr RT_API_ATTRS int compute(T x) { return std::ilogb(x); }
145 };
146 
147 template <typename T> struct ISINFTy {
148   static constexpr RT_API_ATTRS bool compute(T x) { return std::isinf(x); }
149 };
150 
151 template <typename T> struct ISNANTy {
152   static constexpr RT_API_ATTRS bool compute(T x) { return std::isnan(x); }
153 };
154 
155 template <typename T> struct LDEXPTy {
156   template <typename ET> static constexpr RT_API_ATTRS T compute(T x, ET e) {
157     return std::ldexp(x, e);
158   }
159 };
160 
161 template <typename T> struct MAXTy {
162   static constexpr RT_API_ATTRS T compute() {
163     return std::numeric_limits<T>::max();
164   }
165 };
166 
167 #if HAS_LDBL128 || HAS_FLOAT128
168 template <> struct MAXTy<CppTypeFor<TypeCategory::Real, 16>> {
169   static CppTypeFor<TypeCategory::Real, 16> compute() {
170     return MaxOrMinIdentity<TypeCategory::Real, 16, true>::Value();
171   }
172 };
173 #endif
174 
175 template <int PREC, typename T> struct MINTy {
176   static constexpr RT_API_ATTRS T compute() { return MinValue<PREC, T>::get(); }
177 };
178 
179 template <typename T> struct QNANTy {
180   static constexpr RT_API_ATTRS T compute() {
181     return std::numeric_limits<T>::quiet_NaN();
182   }
183 };
184 
185 template <typename T> struct SQRTTy {
186   static constexpr RT_API_ATTRS T compute(T x) { return std::sqrt(x); }
187 };
188 
189 // EXPONENT (16.9.75)
190 template <typename RESULT, typename ARG>
191 inline RT_API_ATTRS RESULT Exponent(ARG x) {
192   if (ISINFTy<ARG>::compute(x) || ISNANTy<ARG>::compute(x)) {
193     return MAXTy<RESULT>::compute(); // +/-Inf, NaN -> HUGE(0)
194   } else if (x == 0) {
195     return 0; // 0 -> 0
196   } else {
197     return ILOGBTy<ARG>::compute(x) + 1;
198   }
199 }
200 
201 // FRACTION (16.9.80)
202 template <typename T> inline RT_API_ATTRS T Fraction(T x) {
203   if (ISNANTy<T>::compute(x)) {
204     return x; // NaN -> same NaN
205   } else if (ISINFTy<T>::compute(x)) {
206     return QNANTy<T>::compute(); // +/-Inf -> NaN
207   } else if (x == 0) {
208     return x; // 0 -> same 0
209   } else {
210     int ignoredExp;
211     return FREXPTy<T>::compute(x, &ignoredExp);
212   }
213 }
214 
215 // SET_EXPONENT (16.9.171)
216 template <typename T> inline RT_API_ATTRS T SetExponent(T x, std::int64_t p) {
217   if (ISNANTy<T>::compute(x)) {
218     return x; // NaN -> same NaN
219   } else if (ISINFTy<T>::compute(x)) {
220     return QNANTy<T>::compute(); // +/-Inf -> NaN
221   } else if (x == 0) {
222     return x; // return negative zero if x is negative zero
223   } else {
224     int expo{ILOGBTy<T>::compute(x) + 1};
225     auto ip{static_cast<int>(p - expo)};
226     if (ip != p - expo) {
227       ip = p < 0 ? std::numeric_limits<int>::min()
228                  : std::numeric_limits<int>::max();
229     }
230     return LDEXPTy<T>::compute(x, ip); // x*2**(p-e)
231   }
232 }
233 
234 // MOD & MODULO (16.9.135, .136)
235 template <bool IS_MODULO, typename T>
236 inline RT_API_ATTRS T RealMod(
237     T a, T p, const char *sourceFile, int sourceLine) {
238   if (p == 0) {
239     Terminator{sourceFile, sourceLine}.Crash(
240         IS_MODULO ? "MODULO with P==0" : "MOD with P==0");
241   }
242   if (ISNANTy<T>::compute(a) || ISNANTy<T>::compute(p) ||
243       ISINFTy<T>::compute(a)) {
244     return QNANTy<T>::compute();
245   } else if (IS_MODULO && ISINFTy<T>::compute(p)) {
246     // Other compilers behave consistently for MOD(x, +/-INF)
247     // and always return x. This is probably related to
248     // implementation of std::fmod(). Stick to this behavior
249     // for MOD, but return NaN for MODULO(x, +/-INF).
250     return QNANTy<T>::compute();
251   }
252   T aAbs{ABSTy<T>::compute(a)};
253   T pAbs{ABSTy<T>::compute(p)};
254   if (aAbs <= static_cast<T>(std::numeric_limits<std::int64_t>::max()) &&
255       pAbs <= static_cast<T>(std::numeric_limits<std::int64_t>::max())) {
256     if (auto aInt{static_cast<std::int64_t>(a)}; a == aInt) {
257       if (auto pInt{static_cast<std::int64_t>(p)}; p == pInt) {
258         // Fast exact case for integer operands
259         auto mod{aInt - (aInt / pInt) * pInt};
260         if constexpr (IS_MODULO) {
261           if (mod == 0) {
262             // Return properly signed zero.
263             return pInt > 0 ? T{0} : -T{0};
264           }
265           if ((aInt > 0) != (pInt > 0)) {
266             mod += pInt;
267           }
268         } else {
269           if (mod == 0) {
270             // Return properly signed zero.
271             return aInt > 0 ? T{0} : -T{0};
272           }
273         }
274         return static_cast<T>(mod);
275       }
276     }
277   }
278   if constexpr (std::is_same_v<T, float> || std::is_same_v<T, double> ||
279       std::is_same_v<T, long double>) {
280     // std::fmod() semantics on signed operands seems to match
281     // the requirements of MOD().  MODULO() needs adjustment.
282     T result{std::fmod(a, p)};
283     if constexpr (IS_MODULO) {
284       if ((a < 0) != (p < 0)) {
285         if (result == 0.) {
286           result = -result;
287         } else {
288           result += p;
289         }
290       }
291     }
292     return result;
293   } else {
294     // The standard defines MOD(a,p)=a-AINT(a/p)*p and
295     // MODULO(a,p)=a-FLOOR(a/p)*p, but those definitions lose
296     // precision badly due to cancellation when ABS(a) is
297     // much larger than ABS(p).
298     // Insights:
299     //  - MOD(a,p)=MOD(a-n*p,p) when a>0, p>0, integer n>0, and a>=n*p
300     //  - when n is a power of two, n*p is exact
301     //  - as a>=n*p, a-n*p does not round.
302     // So repeatedly reduce a by all n*p in decreasing order of n;
303     // what's left is the desired remainder.  This is basically
304     // the same algorithm as arbitrary precision binary long division,
305     // discarding the quotient.
306     T tmp{aAbs};
307     for (T adj{SetExponent(pAbs, Exponent<int>(aAbs))}; tmp >= pAbs; adj /= 2) {
308       if (tmp >= adj) {
309         tmp -= adj;
310         if (tmp == 0) {
311           break;
312         }
313       }
314     }
315     if (a < 0) {
316       tmp = -tmp;
317     }
318     if constexpr (IS_MODULO) {
319       if ((a < 0) != (p < 0)) {
320         if (tmp == 0.) {
321           tmp = -tmp;
322         } else {
323           tmp += p;
324         }
325       }
326     }
327     return tmp;
328   }
329 }
330 
331 // RRSPACING (16.9.164)
332 template <int PREC, typename T> inline RT_API_ATTRS T RRSpacing(T x) {
333   if (ISNANTy<T>::compute(x)) {
334     return x; // NaN -> same NaN
335   } else if (ISINFTy<T>::compute(x)) {
336     return QNANTy<T>::compute(); // +/-Inf -> NaN
337   } else if (x == 0) {
338     return 0; // 0 -> 0
339   } else {
340     return LDEXPTy<T>::compute(
341         ABSTy<T>::compute(x), PREC - (ILOGBTy<T>::compute(x) + 1));
342   }
343 }
344 
345 // SPACING (16.9.180)
346 template <int PREC, typename T> inline RT_API_ATTRS T Spacing(T x) {
347   T tiny{MINTy<PREC, T>::compute()};
348   if (ISNANTy<T>::compute(x)) {
349     return x; // NaN -> same NaN
350   } else if (ISINFTy<T>::compute(x)) {
351     return QNANTy<T>::compute(); // +/-Inf -> NaN
352   } else if (x == 0) { // 0 -> TINY(x)
353     return tiny;
354   } else {
355     T result{LDEXPTy<T>::compute(
356         static_cast<T>(1.0), ILOGBTy<T>::compute(x) + 1 - PREC)}; // 2**(e-p)
357     // All compilers return TINY(x) for |x| <= TINY(x), but differ over whether
358     // SPACING(x) can be < TINY(x) for |x| > TINY(x).  The most common precedent
359     // is to never return a value < TINY(x).
360     return result <= tiny ? tiny : result;
361   }
362 }
363 
364 // ERFC_SCALED (16.9.71)
365 template <typename T> inline RT_API_ATTRS T ErfcScaled(T arg) {
366   return common::ErfcScaled(arg);
367 }
368 
369 } // namespace Fortran::runtime
370 
371 #endif // FORTRAN_RUNTIME_NUMERIC_TEMPLATES_H_
372