Project
Loading...
Searching...
No Matches
GPUCommonDoubleBinary64.h
Go to the documentation of this file.
1// Copyright 2019-2026 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
14
15#ifndef GPUCOMMONDOUBLEBINARY64_H
16#define GPUCOMMONDOUBLEBINARY64_H
17
18#if !defined(__METAL__) && !defined(GPUCA_B64_HOST_REFERENCE)
19#error "the emulated binary64 is for Metal, which has no double of its own; every other backend has a real one. Define GPUCA_B64_HOST_REFERENCE to build it on the host as a test reference."
20#endif
21
22#include "GPUCommonDef.h"
23#ifndef GPUCA_GPUCODE_DEVICE
24#include <cstdint>
25#endif
26
27// IEEE-754 binary64 in software, for a device that has no double at all. Round to
28// nearest even only, with subnormals, infinities and NaNs; NaN propagation follows
29// the ARM64 order, so an Apple host is a bit-exact reference down to the payload.
30// There is no fused multiply-add, no square root and no other rounding mode.
31//
32// Addition, subtraction, multiplication, division and the conversions to and from
33// float and the 32-bit integers are exact, so a Metal build reproduces the CPU
34// result bit for bit wherever it uses only those. sin and cos come from fdlibm and
35// land within 2 ulp of libm rather than matching it.
36//
37// It costs of the order of a hundred times plain float on an M-series GPU, which
38// the tracking can afford because double is a small fraction of its floating point
39// work.
40
41namespace o2::gpu
42{
43
44namespace binary64_detail
45{
46
47#ifdef __METAL__
48typedef ulong u64;
49typedef long i64;
50typedef uint u32;
51#else
52typedef uint64_t u64;
53typedef int64_t i64;
54typedef uint32_t u32;
55#endif
56
57#define GPUCA_B64_ALWAYS inline __attribute__((always_inline))
58#ifdef __METAL__
59// The arithmetic has to stay out of line: inlined into a kernel it drops the
60// occupancy (maxTotalThreadsPerThreadgroup 832 -> 384) and runs two to five
61// times slower than the call.
62#define GPUCA_B64_OP __attribute__((noinline))
63#else
64#define GPUCA_B64_OP inline
65#endif
66
67#ifdef __METAL__
68// metal::clz is not constant-evaluable, which would keep the whole class out of
69// constant expressions; the builtin folds to the same ctlz at run time.
70GPUCA_B64_ALWAYS constexpr int32_t clz64(u64 x) { return x ? __builtin_clzl(x) : 64; }
71GPUCA_B64_ALWAYS constexpr int32_t clz32(u32 x) { return x ? __builtin_clz(x) : 32; }
72GPUCA_B64_ALWAYS constexpr u32 asu32(float f) { return as_type<u32>(f); }
73GPUCA_B64_ALWAYS constexpr float asf32(u32 u) { return as_type<float>(u); }
74#else
75GPUCA_B64_ALWAYS constexpr int32_t clz64(u64 x) { return x ? __builtin_clzll(x) : 64; }
76GPUCA_B64_ALWAYS constexpr int32_t clz32(u32 x) { return x ? __builtin_clz(x) : 32; }
77
78GPUCA_B64_ALWAYS constexpr u32 asu32(float f) { return __builtin_bit_cast(uint32_t, f); }
79GPUCA_B64_ALWAYS constexpr float asf32(u32 u) { return __builtin_bit_cast(float, u); }
80#endif
81
82// The high half of a 64x64 product, from 32-bit partial products. Neither
83// metal::mulhi nor __int128 can appear in a constant expression, and the
84// program-scope constants are constexpr.
86{
87 const u64 al = a & 0xffffffffu, ah = a >> 32, bl = b & 0xffffffffu, bh = b >> 32;
88 const u64 ll = al * bl, lh = al * bh, hl = ah * bl, hh = ah * bh;
89 const u64 mid = (ll >> 32) + (lh & 0xffffffffu) + (hl & 0xffffffffu);
90 return hh + (lh >> 32) + (hl >> 32) + (mid >> 32);
91}
93{
94 u64 h = mulhiu((u64)a, (u64)b);
95 h -= (a < 0) ? (u64)b : 0ULL;
96 h -= (b < 0) ? (u64)a : 0ULL;
97 return (i64)h;
98}
99
100#define GPUCA_B64_SIGN 0x8000000000000000ULL
101#define GPUCA_B64_FRAC 0x000fffffffffffffULL
102#define GPUCA_B64_IMPL 0x0010000000000000ULL
103#define GPUCA_B64_QUIET 0x0008000000000000ULL
104#define GPUCA_B64_INF 0x7ff0000000000000ULL
105#define GPUCA_B64_DNAN 0x7ff8000000000000ULL // ARM default NaN
106
107// right shift keeping a sticky bit; any count >= 0, no shift count ever reaches 64
108GPUCA_B64_ALWAYS constexpr u64 shrJam(u64 m, int32_t s)
109{
110 const int32_t sc = s > 63 ? 63 : s;
111 const u64 r = m >> sc;
112 const u64 lost = (m << (63 - sc)) << 1;
113 const bool big = s > 63;
114 const u64 rr = big ? 0ULL : r;
115 const u64 st = big ? m : lost;
116 return rr | (st != 0 ? 1ULL : 0ULL);
117}
118
119// m: leading bit at 62 for a normal result, 10 guard bits below the 53-bit significand,
120// value = m * 2^(e - 1085) with e the biased exponent. Packing (e-1)<<52 + m lets a
121// rounding carry ripple into the exponent.
122GPUCA_B64_ALWAYS constexpr u64 roundPack(u64 sign, int32_t e, u64 m)
123{
124 if (e >= 0x7ff) {
125 return sign | GPUCA_B64_INF;
126 }
127 if (e <= 0) {
128 m = shrJam(m, 1 - e);
129 e = 1;
130 }
131 const u64 r = m & 0x3ffULL;
132 m >>= 10;
133 if (r > 0x200ULL || (r == 0x200ULL && (m & 1ULL))) {
134 ++m;
135 }
136 return sign | (((u64)(e - 1) << 52) + m);
137}
138
139// an sNaN operand wins over a qNaN one, among equals the first wins, the result is quietened
140GPUCA_B64_ALWAYS constexpr u64 propNaN(u64 a, u64 b, bool an, bool bn)
141{
142 const bool as = an && !(a & GPUCA_B64_QUIET), bs = bn && !(b & GPUCA_B64_QUIET);
143 if (as) {
144 return a | GPUCA_B64_QUIET;
145 }
146 if (bs) {
147 return b | GPUCA_B64_QUIET;
148 }
149 return an ? a : b;
150}
151
152GPUCA_B64_OP constexpr u64 addsub(u64 a, u64 b0, bool neg)
153{
154 const u64 b = neg ? (b0 ^ GPUCA_B64_SIGN) : b0;
155 const bool sw = (a & ~GPUCA_B64_SIGN) < (b & ~GPUCA_B64_SIGN);
156 const u64 x = sw ? b : a, y = sw ? a : b; // |x| >= |y|
157 int32_t ex = (int32_t)((x >> 52) & 0x7ff), ey = (int32_t)((y >> 52) & 0x7ff);
158 u64 mx = x & GPUCA_B64_FRAC, my = y & GPUCA_B64_FRAC;
159 if (ex == 0x7ff) { // y can only be inf/NaN if x is too
160 const bool an = ((a >> 52) & 0x7ff) == 0x7ff && (a & GPUCA_B64_FRAC) != 0, bn = ((b0 >> 52) & 0x7ff) == 0x7ff && (b0 & GPUCA_B64_FRAC) != 0;
161 if (an || bn) {
162 return propNaN(a, b0, an, bn);
163 }
164 if (ey == 0x7ff) {
165 return ((x ^ y) & GPUCA_B64_SIGN) ? GPUCA_B64_DNAN : x;
166 }
167 return x;
168 }
169 const u64 sx = x & GPUCA_B64_SIGN;
170 const bool sub = ((x ^ y) >> 63) != 0;
171 mx = (ex ? (mx | GPUCA_B64_IMPL) : mx) << 10;
172 ex = ex ? ex : 1;
173 my = (ey ? (my | GPUCA_B64_IMPL) : my) << 10;
174 ey = ey ? ey : 1;
175 my = shrJam(my, ex - ey);
176 const u64 s = sub ? mx - my : mx + my;
177 const bool carry = (s >> 63) != 0;
178 const int32_t lz = clz64(s) - 1; // -1 on carry, 63 on zero
179 const u64 sN = carry ? ((s >> 1) | (s & 1ULL)) : (s << (lz & 63));
180 const int32_t eN = carry ? ex + 1 : ex - lz;
181 const bool zero = s == 0;
182 return roundPack((zero && sub) ? 0ULL : sx, zero ? -1 : eN, sN);
183}
184
186{
187 const u64 sign = (a ^ b) & GPUCA_B64_SIGN;
188 int32_t ea = (int32_t)((a >> 52) & 0x7ff), eb = (int32_t)((b >> 52) & 0x7ff);
189 u64 ma = a & GPUCA_B64_FRAC, mb = b & GPUCA_B64_FRAC;
190 if (ea == 0x7ff || eb == 0x7ff) {
191 const bool an = ea == 0x7ff && ma != 0, bn = eb == 0x7ff && mb != 0;
192 if (an || bn) {
193 return propNaN(a, b, an, bn);
194 }
195 if ((ea == 0 && ma == 0) || (eb == 0 && mb == 0)) {
196 return GPUCA_B64_DNAN; // inf * 0
197 }
198 return sign | GPUCA_B64_INF;
199 }
200 if (ea == 0) {
201 if (ma == 0) {
202 return sign;
203 }
204 const int32_t lz = clz64(ma) - 11;
205 ma <<= lz;
206 ea = 1 - lz;
207 } else {
208 ma |= GPUCA_B64_IMPL;
209 }
210 if (eb == 0) {
211 if (mb == 0) {
212 return sign;
213 }
214 const int32_t lz = clz64(mb) - 11;
215 mb <<= lz;
216 eb = 1 - lz;
217 } else {
218 mb |= GPUCA_B64_IMPL;
219 }
220 const u64 lo = ma * mb, hi = mulhiu(ma, mb); // 106-bit product in [2^104, 2^106)
221 const bool top = (hi & (1ULL << 41)) != 0;
222 const u64 m1 = (hi << 21) | (lo >> 43), m0 = (hi << 22) | (lo >> 42);
223 const u64 st = top ? (lo & ((1ULL << 43) - 1)) : (lo & ((1ULL << 42) - 1));
224 const u64 m = (top ? m1 : m0) | (st != 0 ? 1ULL : 0ULL);
225 return roundPack(sign, ea + eb - 1023 + (top ? 1 : 0), m);
226}
227
229{
230 const u64 sign = (a ^ b) & GPUCA_B64_SIGN;
231 int32_t ea = (int32_t)((a >> 52) & 0x7ff), eb = (int32_t)((b >> 52) & 0x7ff);
232 u64 ma = a & GPUCA_B64_FRAC, mb = b & GPUCA_B64_FRAC;
233 if (ea == 0x7ff || eb == 0x7ff) {
234 const bool an = ea == 0x7ff && ma != 0, bn = eb == 0x7ff && mb != 0;
235 if (an || bn) {
236 return propNaN(a, b, an, bn);
237 }
238 if (ea == 0x7ff && eb == 0x7ff) {
239 return GPUCA_B64_DNAN; // inf / inf
240 }
241 return ea == 0x7ff ? (sign | GPUCA_B64_INF) : sign; // inf / x, x / inf
242 }
243 if (eb == 0 && mb == 0) {
244 return (ea == 0 && ma == 0) ? GPUCA_B64_DNAN : (sign | GPUCA_B64_INF); // x / 0
245 }
246 if (ea == 0) {
247 if (ma == 0) {
248 return sign;
249 }
250 const int32_t lz = clz64(ma) - 11;
251 ma <<= lz;
252 ea = 1 - lz;
253 } else {
254 ma |= GPUCA_B64_IMPL;
255 }
256 if (eb == 0) {
257 const int32_t lz = clz64(mb) - 11;
258 mb <<= lz;
259 eb = 1 - lz;
260 } else {
261 mb |= GPUCA_B64_IMPL;
262 }
263 const bool lt = ma < mb;
264 const int32_t e = ea - eb + 1023 - (lt ? 1 : 0);
265 const u64 A2 = lt ? (ma << 1) : ma; // A2 / mb in [1, 2)
266 // reciprocal R ~ 2^114 / mb in (2^61, 2^62], seeded from a float division on the top 24 bits
267 const u32 rb = asu32(1.0f / (float)(u32)(mb >> 29));
268 u64 R = (u64)((rb & 0x7fffffu) | 0x800000u) << ((int32_t)((rb >> 23) & 0xff) - 127 + 62);
269 const u64 Bn = mb << 11;
270 for (int32_t it = 0; it < 2; ++it) {
271 const i64 E = (i64)(1ULL << 61) - (i64)mulhiu(Bn, R);
272 R = (u64)((i64)R + mulhis((i64)R, E * 8)); // not E << 3: shifting a negative value is not a constant expression
273 }
274 R = R > (1ULL << 62) ? (1ULL << 62) : R;
275 // Q ~ A2 * 2^62 / mb, then the exact remainder, which fits in a signed 64-bit
276 // word because Q is within a few units
277 u64 Q = mulhiu(A2 << 10, (R << 2) - 1);
278 i64 rem = (i64)((A2 << 62) - Q * mb);
279 const i64 adj = mulhis(rem, (i64)R) >> 50;
280 Q = (u64)((i64)Q + adj);
281 rem -= adj * (i64)mb;
282 // after adj the remainder is within one divisor of [0, mb): one predicated step each way
283 const bool ng = rem < 0;
284 Q = ng ? Q - 1 : Q;
285 rem = ng ? rem + (i64)mb : rem;
286 const bool bg = rem >= (i64)mb;
287 Q = bg ? Q + 1 : Q;
288 rem = bg ? rem - (i64)mb : rem;
289 return roundPack(sign, e, Q | (rem != 0 ? 1ULL : 0ULL));
290}
291
292// an int32 or a uint32 always fits the 53-bit significand, so these are exact
294{
295 if (x == 0) {
296 return 0ULL;
297 }
298 const int32_t lz = clz32(x);
299 return ((u64)(31 - lz + 1023) << 52) | (((u64)x << (21 + lz)) & GPUCA_B64_FRAC);
300}
301
302GPUCA_B64_ALWAYS constexpr u64 fromI32(int32_t x)
303{
304 return fromU32(x < 0 ? (u32)(-(i64)x) : (u32)x) | (x < 0 ? GPUCA_B64_SIGN : 0ULL);
305}
306
308{
309 const u32 u = asu32(f);
310 const u64 sign = (u64)(u & 0x80000000u) << 32;
311 int32_t e = (int32_t)((u >> 23) & 0xff);
312 u32 m = u & 0x7fffffu;
313 if (e == 0xff) {
314 return sign | GPUCA_B64_INF | ((u64)m << 29) | (m ? GPUCA_B64_QUIET : 0ULL);
315 }
316 if (e == 0) {
317 if (m == 0) {
318 return sign;
319 }
320 const int32_t lz = clz32(m) - 8;
321 m <<= lz;
322 e = 1 - lz;
323 }
324 return sign | ((u64)(e - 127 + 1023) << 52) | ((u64)(m & 0x7fffffu) << 29);
325}
326
327// binary64 -> binary32, round to nearest even, subnormals, inf, NaN (payload kept, quietened)
328GPUCA_B64_OP constexpr float toFloat(u64 d)
329{
330 const u32 sign = (u32)(d >> 32) & 0x80000000u;
331 const int32_t be = (int32_t)((d >> 52) & 0x7ff);
332 const u32 man = (u32)((d & GPUCA_B64_FRAC) >> 29);
333 const u32 drop = (u32)d & 0x1fffffffu;
334 u32 bits = 0;
335 if (be == 0x7ff) {
336 bits = sign | 0x7f800000u | man | ((d & GPUCA_B64_FRAC) ? 0x400000u : 0u);
337 } else if (be == 0) {
338 bits = sign;
339 } else {
340 const int32_t e = be - 1023 + 127;
341 if (e >= 0xff) {
342 bits = sign | 0x7f800000u;
343 } else if (e > 0) {
344 bits = sign | ((u32)e << 23) | man;
345 if ((drop & 0x10000000u) && ((drop & 0x0fffffffu) || (man & 1u))) {
346 bits += 1u;
347 }
348 } else if (e > -24) {
349 const u32 full = man | 0x800000u;
350 const u32 sh = (u32)(1 - e);
351 const u32 lost = full & ((1u << sh) - 1u);
352 const u32 halfb = 1u << (sh - 1);
353 u32 sub = full >> sh;
354 if (lost > halfb || (lost == halfb && ((sub & 1u) || drop))) {
355 sub += 1u;
356 }
357 bits = sign | sub;
358 } else {
359 bits = sign;
360 }
361 }
362 return asf32(bits);
363}
364
365} // namespace binary64_detail
366
368{
369 public:
371 GPUdi() constexpr GPUdoubleBinary64(float v) : mBits(binary64_detail::fromFloat(v)) {}
372 GPUdi() constexpr operator float() const { return binary64_detail::toFloat(mBits); }
373
374 GPUdi() static constexpr GPUdoubleBinary64 fromBits(binary64_detail::u64 b) { return GPUdoubleBinary64(b, FromBits{}); }
375 GPUdi() constexpr binary64_detail::u64 bits() const { return mBits; }
376
377 GPUdi() constexpr GPUdoubleBinary64 operator-() const { return fromBits(mBits ^ GPUCA_B64_SIGN); }
378 GPUdi() constexpr GPUdoubleBinary64 operator+(GPUdoubleBinary64 b) const { return fromBits(binary64_detail::addsub(mBits, b.mBits, false)); }
379 GPUdi() constexpr GPUdoubleBinary64 operator-(GPUdoubleBinary64 b) const { return fromBits(binary64_detail::addsub(mBits, b.mBits, true)); }
380 GPUdi() constexpr GPUdoubleBinary64 operator*(GPUdoubleBinary64 b) const { return fromBits(binary64_detail::mul(mBits, b.mBits)); }
381 GPUdi() constexpr GPUdoubleBinary64 operator/(GPUdoubleBinary64 b) const { return fromBits(binary64_detail::div(mBits, b.mBits)); }
382
383 GPUdi() constexpr GPUdoubleBinary64 operator+(float b) const { return *this + GPUdoubleBinary64(b); }
384 GPUdi() constexpr GPUdoubleBinary64 operator-(float b) const { return *this - GPUdoubleBinary64(b); }
385 GPUdi() constexpr GPUdoubleBinary64 operator*(float b) const { return *this * GPUdoubleBinary64(b); }
386 GPUdi() constexpr GPUdoubleBinary64 operator/(float b) const { return *this / GPUdoubleBinary64(b); }
387#ifndef __METAL__
388 GPUdi() constexpr GPUdoubleBinary64 operator+(double b) const { return *this + (float)b; }
389 GPUdi() constexpr GPUdoubleBinary64 operator-(double b) const { return *this - (float)b; }
390 GPUdi() constexpr GPUdoubleBinary64 operator*(double b) const { return *this * (float)b; }
391 GPUdi() constexpr GPUdoubleBinary64 operator/(double b) const { return *this / (float)b; }
392#endif
393
394 // integral operands: an exact match, so `2 * x` does not sit ambiguously between
395 // converting the int up and converting *this down
396 GPUdi() constexpr GPUdoubleBinary64 operator+(int32_t b) const { return *this + fromBits(binary64_detail::fromI32(b)); }
397 GPUdi() constexpr GPUdoubleBinary64 operator-(int32_t b) const { return *this - fromBits(binary64_detail::fromI32(b)); }
398 GPUdi() constexpr GPUdoubleBinary64 operator*(int32_t b) const { return *this * fromBits(binary64_detail::fromI32(b)); }
399 GPUdi() constexpr GPUdoubleBinary64 operator/(int32_t b) const { return *this / fromBits(binary64_detail::fromI32(b)); }
400 GPUdi() constexpr GPUdoubleBinary64 operator+(uint32_t b) const { return *this + fromBits(binary64_detail::fromU32(b)); }
401 GPUdi() constexpr GPUdoubleBinary64 operator-(uint32_t b) const { return *this - fromBits(binary64_detail::fromU32(b)); }
402 GPUdi() constexpr GPUdoubleBinary64 operator*(uint32_t b) const { return *this * fromBits(binary64_detail::fromU32(b)); }
403 GPUdi() constexpr GPUdoubleBinary64 operator/(uint32_t b) const { return *this / fromBits(binary64_detail::fromU32(b)); }
404#ifdef __METAL__
405 // the same surface for an object that lives in the constant address space, which
406 // a generic `this` does not reach
407 GPUdi() constexpr GPUdoubleBinary64(float v) constant : mBits(binary64_detail::fromFloat(v)) {}
408 GPUdi() constexpr operator float() constant { return binary64_detail::toFloat(mBits); }
409 GPUdi() constexpr GPUdoubleBinary64 operator+(GPUdoubleBinary64 b) constant { return fromBits(binary64_detail::addsub(mBits, b.mBits, false)); }
410 GPUdi() constexpr GPUdoubleBinary64 operator-(GPUdoubleBinary64 b) constant { return fromBits(binary64_detail::addsub(mBits, b.mBits, true)); }
411 GPUdi() constexpr GPUdoubleBinary64 operator*(GPUdoubleBinary64 b) constant { return fromBits(binary64_detail::mul(mBits, b.mBits)); }
412 GPUdi() constexpr GPUdoubleBinary64 operator/(GPUdoubleBinary64 b) constant { return fromBits(binary64_detail::div(mBits, b.mBits)); }
413 GPUdi() constexpr GPUdoubleBinary64 operator+(float b) constant { return *this + GPUdoubleBinary64(b); }
414 GPUdi() constexpr GPUdoubleBinary64 operator-(float b) constant { return *this - GPUdoubleBinary64(b); }
415 GPUdi() constexpr GPUdoubleBinary64 operator*(float b) constant { return *this * GPUdoubleBinary64(b); }
416 GPUdi() constexpr GPUdoubleBinary64 operator/(float b) constant { return *this / GPUdoubleBinary64(b); }
417 GPUdi() constexpr GPUdoubleBinary64 operator*(int32_t b) constant { return *this * fromBits(binary64_detail::fromI32(b)); }
418 GPUdi() constexpr GPUdoubleBinary64 operator/(int32_t b) constant { return *this / fromBits(binary64_detail::fromI32(b)); }
419#endif
420
421 GPUdi() GPUdoubleBinary64& operator+=(GPUdoubleBinary64 b) { return *this = *this + b; }
422 GPUdi() GPUdoubleBinary64& operator-=(GPUdoubleBinary64 b) { return *this = *this - b; }
423 GPUdi() GPUdoubleBinary64& operator*=(GPUdoubleBinary64 b) { return *this = *this * b; }
424 GPUdi() GPUdoubleBinary64& operator/=(GPUdoubleBinary64 b) { return *this = *this / b; }
425
426 private:
427 struct FromBits {
428 };
429 GPUdi() constexpr GPUdoubleBinary64(binary64_detail::u64 b, FromBits) : mBits(b) {}
430
432};
433
434static_assert(sizeof(GPUdoubleBinary64) == 8, "the emulated double must match the size of a real one");
435static_assert(alignof(GPUdoubleBinary64) == 8, "the emulated double must match the alignment of a real one");
436
437GPUdi() constexpr GPUdoubleBinary64 operator+(int32_t a, GPUdoubleBinary64 b) { return GPUdoubleBinary64::fromBits(binary64_detail::fromI32(a)) + b; }
438GPUdi() constexpr GPUdoubleBinary64 operator-(int32_t a, GPUdoubleBinary64 b) { return GPUdoubleBinary64::fromBits(binary64_detail::fromI32(a)) - b; }
439GPUdi() constexpr GPUdoubleBinary64 operator*(int32_t a, GPUdoubleBinary64 b) { return GPUdoubleBinary64::fromBits(binary64_detail::fromI32(a)) * b; }
440GPUdi() constexpr GPUdoubleBinary64 operator/(int32_t a, GPUdoubleBinary64 b) { return GPUdoubleBinary64::fromBits(binary64_detail::fromI32(a)) / b; }
441GPUdi() constexpr GPUdoubleBinary64 operator+(uint32_t a, GPUdoubleBinary64 b) { return GPUdoubleBinary64::fromBits(binary64_detail::fromU32(a)) + b; }
442GPUdi() constexpr GPUdoubleBinary64 operator-(uint32_t a, GPUdoubleBinary64 b) { return GPUdoubleBinary64::fromBits(binary64_detail::fromU32(a)) - b; }
443GPUdi() constexpr GPUdoubleBinary64 operator*(uint32_t a, GPUdoubleBinary64 b) { return GPUdoubleBinary64::fromBits(binary64_detail::fromU32(a)) * b; }
444GPUdi() constexpr GPUdoubleBinary64 operator/(uint32_t a, GPUdoubleBinary64 b) { return GPUdoubleBinary64::fromBits(binary64_detail::fromU32(a)) / b; }
445GPUdi() constexpr GPUdoubleBinary64 operator+(float a, GPUdoubleBinary64 b) { return GPUdoubleBinary64(a) + b; }
446GPUdi() constexpr GPUdoubleBinary64 operator-(float a, GPUdoubleBinary64 b) { return GPUdoubleBinary64(a) - b; }
447GPUdi() constexpr GPUdoubleBinary64 operator*(float a, GPUdoubleBinary64 b) { return GPUdoubleBinary64(a) * b; }
448GPUdi() constexpr GPUdoubleBinary64 operator/(float a, GPUdoubleBinary64 b) { return GPUdoubleBinary64(a) / b; }
449#ifndef __METAL__
450GPUdi() constexpr GPUdoubleBinary64 operator+(double a, GPUdoubleBinary64 b) { return GPUdoubleBinary64((float)a) + b; }
451GPUdi() constexpr GPUdoubleBinary64 operator-(double a, GPUdoubleBinary64 b) { return GPUdoubleBinary64((float)a) - b; }
452GPUdi() constexpr GPUdoubleBinary64 operator*(double a, GPUdoubleBinary64 b) { return GPUdoubleBinary64((float)a) * b; }
453GPUdi() constexpr GPUdoubleBinary64 operator/(double a, GPUdoubleBinary64 b) { return GPUdoubleBinary64((float)a) / b; }
454#endif
455
456// rounds once, as `someFloat += someDouble` does on the host
457#ifdef __METAL__
458GPUdi() thread float& operator+=(thread float& a, GPUdoubleBinary64 b) { return a = (float)(GPUdoubleBinary64(a) + b); }
459GPUdi() device float& operator+=(device float& a, GPUdoubleBinary64 b) { return a = (float)(GPUdoubleBinary64(a) + b); }
460GPUdi() threadgroup float& operator+=(threadgroup float& a, GPUdoubleBinary64 b) { return a = (float)(GPUdoubleBinary64(a) + b); }
461#else
462GPUdi() float& operator+=(float& a, GPUdoubleBinary64 b) { return a = (float)(GPUdoubleBinary64(a) + b); }
463#endif
464
465namespace binary64_detail
466{
467// sin and cos are fdlibm's __kernel_sin / __kernel_cos and the medium-range
468// branch of __ieee754_rem_pio2. The coefficients are spelled as bit patterns
469// because MSL has no double literals. The argument reduction is exact for
470// |x| <= 2^19 * pi/2; beyond that the accuracy degrades gracefully.
471GPUCA_B64_OP GPUdoubleBinary64 kernelSin(GPUdoubleBinary64 x, GPUdoubleBinary64 y, bool iy)
472{
473 typedef GPUdoubleBinary64 b64;
474 const b64 S1 = b64::fromBits(0xBFC5555555555549ULL);
475 const b64 S2 = b64::fromBits(0x3F8111111110F8A6ULL);
476 const b64 S3 = b64::fromBits(0xBF2A01A019C161D5ULL);
477 const b64 S4 = b64::fromBits(0x3EC71DE357B1FE7DULL);
478 const b64 S5 = b64::fromBits(0xBE5AE5E68A2B9CEBULL);
479 const b64 S6 = b64::fromBits(0x3DE5D93A5ACFD57CULL);
480 const b64 z = x * x;
481 const b64 v = z * x;
482 const b64 r = S2 + z * (S3 + z * (S4 + z * (S5 + z * S6)));
483 if (!iy) {
484 return x + v * (S1 + z * r);
485 }
486 return x - ((z * (b64::fromBits(0x3FE0000000000000ULL) * y - v * r) - y) - v * S1);
487}
488
489GPUCA_B64_OP GPUdoubleBinary64 kernelCos(GPUdoubleBinary64 x, GPUdoubleBinary64 y)
490{
491 typedef GPUdoubleBinary64 b64;
492 const b64 C1 = b64::fromBits(0x3FA555555555554CULL);
493 const b64 C2 = b64::fromBits(0xBF56C16C16C15177ULL);
494 const b64 C3 = b64::fromBits(0x3EFA01A019CB1590ULL);
495 const b64 C4 = b64::fromBits(0xBE927E4F809C52ADULL);
496 const b64 C5 = b64::fromBits(0x3E21EE9EBDB4B1C4ULL);
497 const b64 C6 = b64::fromBits(0xBDA8FAE9BE8838D4ULL);
498 const b64 one = b64::fromBits(0x3FF0000000000000ULL);
499 const b64 oneHalf = b64::fromBits(0x3FE0000000000000ULL);
500 const u32 ix = (u32)(x.bits() >> 32) & 0x7fffffffu;
501 const b64 z = x * x;
502 const b64 r = z * (C1 + z * (C2 + z * (C3 + z * (C4 + z * (C5 + z * C6)))));
503 if (ix < 0x3FD33333u) { // |x| < 0.3
504 return one - (oneHalf * z - (z * r - x * y));
505 }
506 const b64 qx = (ix > 0x3FE90000u) ? b64::fromBits(0x3FD2000000000000ULL) : b64::fromBits((u64)(ix - 0x00200000u) << 32); // 0.28125, else |x| / 4
507 return (one - qx) - ((oneHalf * z - qx) - (z * r - x * y));
508}
509
510GPUCA_B64_ALWAYS int32_t truncToInt32(GPUdoubleBinary64 x)
511{
512 const u64 b = x.bits();
513 const int32_t e = (int32_t)((b >> 52) & 0x7ff) - 1023;
514 if (e < 0) {
515 return 0;
516 }
517 const u64 m = (b & GPUCA_B64_FRAC) | GPUCA_B64_IMPL;
518 const int32_t v = (int32_t)(e >= 52 ? (m << (e - 52)) : (m >> (52 - e)));
519 return (b & GPUCA_B64_SIGN) ? -v : v;
520}
521
522struct SinCosPair {
523 GPUdoubleBinary64 s, c;
524};
525
526GPUCA_B64_OP SinCosPair sincos(GPUdoubleBinary64 x)
527{
528 typedef GPUdoubleBinary64 b64;
529 const u32 ix = (u32)(x.bits() >> 32) & 0x7fffffffu;
530 SinCosPair out;
531 if (ix >= 0x7FF00000u) { // inf or NaN
532 out.s = out.c = b64::fromBits(GPUCA_B64_DNAN);
533 return out;
534 }
535
536 if (ix < 0x3E400000u) { // |x| < 2^-27, where the sign of a zero x has to survive
537 out.s = x;
538 out.c = b64::fromBits(0x3FF0000000000000ULL);
539 return out;
540 }
541
542 b64 y0 = x, y1 = b64::fromBits(0ULL);
543 int32_t n = 0;
544 const bool reduced = ix > 0x3FE921FBu; // |x| > pi/4
545 if (reduced) {
546 const b64 t = b64::fromBits(x.bits() & ~GPUCA_B64_SIGN);
547 n = truncToInt32(t * b64::fromBits(0x3FE45F306DC9C883ULL) + b64::fromBits(0x3FE0000000000000ULL));
548 const b64 fn = b64((float)n);
549 // Cody-Waite with pi/2 split over three terms, good to 151 bits
550 b64 r = t - fn * b64::fromBits(0x3FF921FB54400000ULL);
551 b64 s = r;
552 b64 w = fn * b64::fromBits(0x3DD0B4611A600000ULL);
553 r = s - w;
554 w = fn * b64::fromBits(0x3BA3198A2E037073ULL) - ((s - r) - w);
555 s = r;
556 w = fn * b64::fromBits(0x3BA3198A2E000000ULL);
557 r = s - w;
558 w = fn * b64::fromBits(0x397B839A252049C1ULL) - ((s - r) - w);
559 y0 = r - w;
560 y1 = (r - y0) - w;
561 if (x.bits() & GPUCA_B64_SIGN) {
562 y0 = -y0;
563 y1 = -y1;
564 n = -n;
565 }
566 }
567
568 switch (n & 3) {
569 case 0:
570 out.s = kernelSin(y0, y1, reduced);
571 out.c = kernelCos(y0, y1);
572 break;
573 case 1:
574 out.s = kernelCos(y0, y1);
575 out.c = -kernelSin(y0, y1, reduced);
576 break;
577 case 2:
578 out.s = -kernelSin(y0, y1, reduced);
579 out.c = -kernelCos(y0, y1);
580 break;
581 default:
582 out.s = -kernelCos(y0, y1);
583 out.c = kernelSin(y0, y1, reduced);
584 break;
585 }
586 return out;
587}
588} // namespace binary64_detail
589
590} // namespace o2::gpu
591
592#endif // GPUCOMMONDOUBLEBINARY64_H
#define GPUCA_B64_IMPL
#define GPUCA_B64_INF
#define GPUCA_B64_ALWAYS
#define GPUCA_B64_FRAC
#define GPUCA_B64_DNAN
#define GPUCA_B64_OP
#define GPUCA_B64_SIGN
#define GPUCA_B64_QUIET
uint32_t one
Definition RawData.h:4
uint32_t c
Definition RawData.h:2
benchmark::State & st
Class for time synchronization of RawReader instances.
GPUdi() static const expr GPUdoubleBinary64 fromBits(binary64_detail
GPUdi() const expr operator float() const
GPUdi() const expr binary64_detail
GPUdDefault() GPUdoubleBinary64()=default
GPUdi() const expr GPUdoubleBinary64(binary64_detail
GPUdi() const expr GPUdoubleBinary64(float v)
GPUdi() GPUdoubleBinary64 &operator-
GLdouble n
Definition glcorearb.h:1982
GLint GLenum GLint x
Definition glcorearb.h:403
const GLfloat * m
Definition glcorearb.h:4066
GLuint GLfloat GLfloat GLfloat GLfloat y1
Definition glcorearb.h:5034
const GLdouble * v
Definition glcorearb.h:832
GLdouble GLdouble GLdouble GLdouble top
Definition glcorearb.h:4077
GLdouble f
Definition glcorearb.h:310
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLint y
Definition glcorearb.h:270
GLenum GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const void * bits
Definition glcorearb.h:4150
GLboolean r
Definition glcorearb.h:1233
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
GLubyte GLubyte GLubyte GLubyte w
Definition glcorearb.h:852
GLuint GLfloat GLfloat y0
Definition glcorearb.h:5034
GLdouble GLdouble GLdouble z
Definition glcorearb.h:843
GPUCA_B64_OP constexpr u64 addsub(u64 a, u64 b0, bool neg)
GPUCA_B64_OP constexpr float toFloat(u64 d)
GPUCA_B64_ALWAYS constexpr u64 propNaN(u64 a, u64 b, bool an, bool bn)
GPUCA_B64_ALWAYS constexpr u64 mulhiu(u64 a, u64 b)
GPUCA_B64_ALWAYS constexpr u64 fromI32(int32_t x)
GPUCA_B64_ALWAYS constexpr int32_t clz64(u64 x)
GPUCA_B64_ALWAYS constexpr i64 mulhis(i64 a, i64 b)
GPUCA_B64_ALWAYS constexpr u64 shrJam(u64 m, int32_t s)
GPUCA_B64_OP constexpr u64 div(u64 a, u64 b)
GPUCA_B64_ALWAYS constexpr u64 roundPack(u64 sign, int32_t e, u64 m)
GPUCA_B64_ALWAYS constexpr float asf32(u32 u)
GPUCA_B64_ALWAYS constexpr int32_t clz32(u32 x)
GPUCA_B64_ALWAYS constexpr u64 fromFloat(float f)
GPUCA_B64_OP constexpr u64 mul(u64 a, u64 b)
GPUCA_B64_ALWAYS constexpr u32 asu32(float f)
GPUCA_B64_ALWAYS constexpr u64 fromU32(u32 x)
GPUdoubleBinary64 b
GPUdi() o2
Definition TrackTRD.h:39
constexpr int ix
Definition TrackUtils.h:69
unsigned long long ULL
TStopwatch sw