Loading...
Searching...
No Matches
ExactReduction.cpp
Go to the documentation of this file.
1/*
2** This file is part of eOn.
3**
4** SPDX-License-Identifier: BSD-3-Clause
5**
6** Copyright (c) 2010--present, eOn Development Team
7** All rights reserved.
8**
9** Repo:
10** https://github.com/TheochemUI/eOn
11*/
12#include "eon/ExactReduction.h"
13
14// orderedDot promises one rounding per multiply and per add; a fused
15// multiply-add would change its bits on FMA hardware only.
16#if defined(__clang__)
17#pragma clang fp contract(off)
18#elif defined(__GNUC__)
19#pragma GCC optimize("fp-contract=off")
20#endif
21
22#include <array>
23#include <bit>
24#include <cmath>
25#include <cstdint>
26#include <cstring>
27
28#if defined(_MSC_VER) && !defined(__SIZEOF_INT128__) && defined(_M_X64)
29#include <intrin.h>
30#endif
31
32namespace eonc::exact {
33namespace {
34
35// A finite double is M * 2^e with M < 2^53 and e >= -1074, so a product of
36// two is P * 2^E with P < 2^106 and E in [-2148, 1942]. Bit k of the
37// accumulator weighs 2^(k - kBase). Chunks of 32 bits sit in signed 64-bit
38// words: every addend piece is below 2^32, so a word absorbs 2^30 pieces
39// between carry normalisations, and signs need no borrow chains.
40constexpr int kBase = 2148;
41constexpr int kTopBit = 1942 + 106 + kBase; // one past the highest product bit
42constexpr int kChunks = kTopBit / 32 + 4; // headroom for carries and sign
43constexpr std::size_t kNormalizeEvery = std::size_t{1} << 30;
44constexpr uint64_t kMask32 = 0xffffffffULL;
45
46struct Decoded {
47 uint64_t m; // significand as an integer, < 2^53
48 int e; // value = m * 2^e
49 bool neg;
50};
51
52inline bool decode(double v, Decoded &d) {
53 uint64_t b;
54 std::memcpy(&b, &v, sizeof b);
55 const auto field = static_cast<int>((b >> 52) & 0x7ff);
56 if (field == 0x7ff) {
57 return false;
58 }
59 const uint64_t frac = b & ((uint64_t{1} << 52) - 1);
60 d.neg = (b >> 63) != 0;
61 if (field == 0) {
62 d.m = frac;
63 d.e = -1074;
64 } else {
65 d.m = frac | (uint64_t{1} << 52);
66 d.e = field - 1075;
67 }
68 return true;
69}
70
71// Full 128-bit product of two significands, as (hi, lo).
72inline void mul64(uint64_t a, uint64_t b, uint64_t &hi, uint64_t &lo) {
73#if defined(__SIZEOF_INT128__)
74 const unsigned __int128 p = static_cast<unsigned __int128>(a) * b;
75 lo = static_cast<uint64_t>(p);
76 hi = static_cast<uint64_t>(p >> 64);
77#elif defined(_MSC_VER) && defined(_M_X64)
78 lo = _umul128(a, b, &hi);
79#else
80 const uint64_t a0 = a & kMask32, a1 = a >> 32;
81 const uint64_t b0 = b & kMask32, b1 = b >> 32;
82 const uint64_t p00 = a0 * b0, p01 = a0 * b1, p10 = a1 * b0, p11 = a1 * b1;
83 const uint64_t mid = (p00 >> 32) + (p01 & kMask32) + (p10 & kMask32);
84 lo = (p00 & kMask32) | (mid << 32);
85 hi = p11 + (p01 >> 32) + (p10 >> 32) + (mid >> 32);
86#endif
87}
88
89class Accumulator {
90public:
91 // Words are cleared when a product first reaches them, so a short
92 // vector clears a handful of words, not all of them. Every read stays
93 // inside [m_lo, m_hi), the cleared window.
94
95 // Adds +-(hi, lo) * 2^e.
96 void add(uint64_t hi, uint64_t lo, int e, bool neg) {
97 if ((hi | lo) == 0) {
98 return;
99 }
100 const int pos = e + kBase;
101 const int c = pos >> 5;
102 const int s = pos & 31;
103 // (hi, lo) * 2^s is below 2^138: five 32-bit pieces from word c up.
104 const uint64_t t0 = lo << s;
105 const uint64_t t1 = (hi << s) | (s != 0 ? lo >> (64 - s) : 0);
106 const uint64_t t2 = s != 0 ? hi >> (64 - s) : 0;
107 if (c < m_lo || c + 5 > m_hi) [[unlikely]] {
108 clear(c, c + 5);
109 }
110 const int64_t sg = neg ? -1 : 1;
111 int64_t *w = m_w.data() + c;
112 w[0] += sg * static_cast<int64_t>(t0 & kMask32);
113 w[1] += sg * static_cast<int64_t>(t0 >> 32);
114 w[2] += sg * static_cast<int64_t>(t1 & kMask32);
115 w[3] += sg * static_cast<int64_t>(t1 >> 32);
116 w[4] += sg * static_cast<int64_t>(t2 & kMask32);
117 }
118
119 // Folds the carries back into words in [0, 2^32), keeping the sign in
120 // the top word, so another 2^30 pieces fit.
121 void fold() {
122 const int64_t sign = normalize();
123 if (m_hi > m_lo) {
124 m_w[static_cast<std::size_t>(m_hi - 1)] += sign * (int64_t{1} << 32);
125 }
126 }
127
128 // Adds another accumulator's exact value to this one. Both are folded
129 // first, so every word stays far inside 64 bits.
130 void absorb(Accumulator &o) {
131 if (o.m_hi <= o.m_lo) {
132 return;
133 }
134 fold();
135 o.fold();
136 if (o.m_lo < m_lo || o.m_hi > m_hi) {
137 clear(o.m_lo, o.m_hi);
138 }
139 for (int k = o.m_lo; k < o.m_hi; ++k) {
140 m_w[static_cast<std::size_t>(k)] += o.m_w[static_cast<std::size_t>(k)];
141 }
142 }
143
144 double round() {
145 const int64_t sign = normalize();
146 if (m_hi <= m_lo) {
147 return 0.0;
148 }
149 const bool neg = sign < 0;
150 if (neg) {
151 // Value = W - 2^(32 m_hi); its magnitude is the two's complement of W
152 // over the words [m_lo, m_hi), whose lower words are zero.
153 int64_t carry = 1;
154 for (int k = m_lo; k < m_hi; ++k) {
155 const int64_t v = ((~m_w[k]) & static_cast<int64_t>(kMask32)) + carry;
156 m_w[k] = v & static_cast<int64_t>(kMask32);
157 carry = v >> 32;
158 }
159 if (carry != 0) {
160 m_w[static_cast<std::size_t>(m_hi)] = carry;
161 ++m_hi;
162 }
163 }
164 int h = m_hi - 1;
165 while (h >= m_lo && m_w[h] == 0) {
166 --h;
167 }
168 if (h < m_lo) {
169 return 0.0;
170 }
171 const int tb =
172 32 * h + 31 - std::countl_zero(static_cast<uint32_t>(m_w[h]));
173 // Lowest kept bit: 53 significant bits, but never below 2^-1074.
174 int lsb = tb - 52;
175 if (lsb < kBase - 1074) {
176 lsb = kBase - 1074;
177 }
178 uint64_t m = bits(lsb, tb - lsb + 1);
179 const bool half = bit(lsb - 1);
180 const bool sticky = below(lsb - 1);
181 if (half && (sticky || (m & 1U) != 0)) {
182 ++m;
183 if (m == (uint64_t{1} << 53)) {
184 m >>= 1;
185 ++lsb;
186 }
187 }
188 // m <= 2^53 times a power of two in range is exact; past 2^1024 it is
189 // the correctly rounded infinity.
190 const double r = std::ldexp(static_cast<double>(m), lsb - kBase);
191 return neg ? -r : r;
192 }
193
194private:
195 // Carries every word of [m_lo, m_hi) into [0, 2^32) and returns the sign
196 // (0 or -1) that the carry out of the top word leaves.
197 int64_t normalize() {
198 if (m_hi <= m_lo) {
199 return 0;
200 }
201 int64_t carry = 0;
202 int k = m_lo;
203 for (; k < m_hi; ++k) {
204 const int64_t v = m_w[k] + carry;
205 m_w[k] = v & static_cast<int64_t>(kMask32);
206 carry = v >> 32; // arithmetic shift: floor division
207 }
208 // A positive carry, or a negative one below -1, spills into the zero
209 // words above.
210 while (carry != 0 && carry != -1) {
211 m_w[static_cast<std::size_t>(k)] = carry & static_cast<int64_t>(kMask32);
212 carry >>= 32;
213 ++k;
214 }
215 m_hi = k;
216 return carry;
217 }
218
219 void clear(int lo, int hi) {
220 if (m_lo >= m_hi) {
221 for (int k = lo; k < hi; ++k) {
222 m_w[static_cast<std::size_t>(k)] = 0;
223 }
224 m_lo = lo;
225 m_hi = hi;
226 return;
227 }
228 for (int k = lo; k < m_lo; ++k) {
229 m_w[static_cast<std::size_t>(k)] = 0;
230 }
231 for (int k = m_hi; k < hi; ++k) {
232 m_w[static_cast<std::size_t>(k)] = 0;
233 }
234 m_lo = lo < m_lo ? lo : m_lo;
235 m_hi = hi > m_hi ? hi : m_hi;
236 }
237
238 int64_t word(int k) const {
239 return (k >= m_lo && k < m_hi) ? m_w[static_cast<std::size_t>(k)] : 0;
240 }
241
242 bool bit(int k) const { return ((word(k >> 5) >> (k & 31)) & 1) != 0; }
243
244 // Any set bit strictly below bit k.
245 bool below(int k) const {
246 const int c = k >> 5;
247 for (int j = m_lo; j < c && j < m_hi; ++j) {
248 if (m_w[static_cast<std::size_t>(j)] != 0) {
249 return true;
250 }
251 }
252 const uint64_t partMask = (uint64_t{1} << (k & 31)) - 1;
253 return (static_cast<uint64_t>(word(c)) & partMask) != 0;
254 }
255
256 // Bits [k, k + width) for width <= 53.
257 uint64_t bits(int k, int width) const {
258 const int c = k >> 5;
259 const int off = k & 31;
260 const uint64_t low = static_cast<uint64_t>(word(c)) |
261 (static_cast<uint64_t>(word(c + 1)) << 32);
262 const uint64_t high = static_cast<uint64_t>(word(c + 2));
263 const uint64_t v = (low >> off) | (off != 0 ? high << (64 - off) : 0);
264 return v & ((uint64_t{1} << width) - 1);
265 }
266
267 std::array<int64_t, kChunks> m_w; // valid inside [m_lo, m_hi) only
268 int m_lo = kChunks;
269 int m_hi = 0;
270};
271
272} // namespace
273
274// Consecutive products usually land on the same words, so one accumulator
275// chains a read-modify-write through memory per product. Four accumulators
276// take the products in turn and break that chain; integer addition makes
277// the split invisible in the result.
278constexpr std::size_t kLanes = 4;
279
280namespace {
281
282// Error-free transformations (Dekker 1971; Knuth's TwoSum). Exact in
283// round to nearest when nothing overflows and, for twoProduct, the
284// product's error does not underflow; certifiedDot checks both.
285inline void twoSum(double a, double b, double &s, double &e) {
286 s = a + b;
287 const double bb = s - a;
288 e = (a - (s - bb)) + (b - bb);
289}
290
291// kFma: the caller runs with FMA enabled (its own ISA or a target("fma")
292// wrapper). Both forms give the same exact error term.
293template <bool kFma>
294inline void twoProduct(double a, double b, double &p, double &e) {
295 p = a * b;
296 if constexpr (kFma) {
297 e = std::fma(a, b, -p);
298 } else {
299 constexpr double kSplitter = 134217729.0; // 2^27 + 1, Veltkamp
300 const double ca = kSplitter * a;
301 const double ah = ca - (ca - a);
302 const double al = a - ah;
303 const double cb = kSplitter * b;
304 const double bh = cb - (cb - b);
305 const double bl = b - bh;
306 e = ((ah * bh - p) + ah * bl + al * bh) + al * bl;
307 }
308}
309
310// Fast path: a compensated dot product (Ogita, Rump and Oishi 2005, Dot2)
311// in four lanes, s + c with the exact sum = s + c_exact, and an a
312// posteriori bound E >= |c - c_exact| from the recursive-summation bound
313// gamma_N * sum |t_i|. If [s + c - E, s + c + E] lies strictly between the
314// midpoints around r = RN(s + c), then r is the correctly rounded exact
315// sum. Otherwise, or when a range guard fails, returns false and the
316// integer accumulator decides. Both paths return RN(exact), so the choice
317// never shows in the bits.
318// Inlined into each wrapper so the FMA instance is compiled for FMA.
319#if defined(__GNUC__)
320#define EONC_EXACT_INLINE inline __attribute__((always_inline))
321#else
322#define EONC_EXACT_INLINE inline
323#endif
324
325template <bool kFma>
326EONC_EXACT_INLINE bool certifiedDotT(const double *x, const double *y,
327 std::size_t n, double &out) {
328 constexpr double u = 0x1p-53;
329 if (n == 0 || static_cast<double>(n) > 0x1p30) {
330 return false;
331 }
332 double s[kLanes] = {0.0, 0.0, 0.0, 0.0};
333 double c[kLanes] = {0.0, 0.0, 0.0, 0.0};
334 double b[kLanes] = {0.0, 0.0, 0.0, 0.0};
335 double maxAbs[kLanes] = {0.0, 0.0, 0.0, 0.0};
336 double minProd[kLanes] = {1.0, 1.0, 1.0, 1.0};
337 const auto term = [&](std::size_t k, double xi, double yi) {
338 double p;
339 double e;
340 twoProduct<kFma>(xi, yi, p, e);
341 double q;
342 twoSum(s[k], p, s[k], q);
343 c[k] += q + e;
344 b[k] += std::fabs(q) + std::fabs(e);
345 const double ax = std::fabs(xi);
346 const double ay = std::fabs(yi);
347 const double m = ax > ay ? ax : ay;
348 maxAbs[k] = m > maxAbs[k] ? m : maxAbs[k];
349 // A nonzero pair whose product underflowed to zero must fail the
350 // guard too, so the test is on the inputs, not on p.
351 const double ap = std::fabs(p);
352 if (xi != 0.0 && yi != 0.0 && ap < minProd[k]) {
353 minProd[k] = ap;
354 }
355 };
356 std::size_t i = 0;
357 for (; i + kLanes <= n; i += kLanes) {
358 for (std::size_t k = 0; k < kLanes; ++k) {
359 term(k, x[i + k], y[i + k]);
360 }
361 }
362 for (std::size_t k = 0; i + k < n; ++k) {
363 term(k, x[i + k], y[i + k]);
364 }
365 // Guards: no overflow anywhere (every |x_i|, |y_i| < 2^480, so every
366 // product and partial sum stays below 2^991), no non-finite input (the
367 // first guard fails on infinity, a NaN makes r NaN below), and no
368 // nonzero pair with a product below 2^-960, so each product error is
369 // representable.
370 double mx = maxAbs[0];
371 double mp = minProd[0];
372 for (std::size_t k = 1; k < kLanes; ++k) {
373 mx = maxAbs[k] > mx ? maxAbs[k] : mx;
374 mp = minProd[k] < mp ? minProd[k] : mp;
375 }
376 if (!(mx < 0x1p480) || !(mp >= 0x1p-960)) {
377 return false;
378 }
379 double s01;
380 double q1;
381 double s23;
382 double q2;
383 double sAll;
384 double q3;
385 twoSum(s[0], s[1], s01, q1);
386 twoSum(s[2], s[3], s23, q2);
387 twoSum(s01, s23, sAll, q3);
388 const double cAll = ((c[0] + c[1]) + (c[2] + c[3])) + ((q1 + q2) + q3);
389 const double bAll = ((b[0] + b[1]) + (b[2] + b[3])) +
390 ((std::fabs(q1) + std::fabs(q2)) + std::fabs(q3));
391 // Every t = q + e (or a combining q) passes through at most N roundings
392 // on its way into cAll, so |cAll - c_exact| <= gamma_N * sum |t|, and
393 // bAll underestimates sum |t| by at most the same factor. Twice gamma_N
394 // * bAll covers both, and the slack covers gradual underflow.
395 const double N = static_cast<double>(n) + 8.0;
396 const double gamma = (N * u) / (1.0 - N * u);
397 const double E = 2.0 * gamma * bAll + N * 0x1p-1070;
398 double r;
399 double d;
400 twoSum(sAll, cAll, r, d);
401 const double ar = std::fabs(r);
402 if (!(ar >= 0x1p-1000) || !(ar < 0x1p1000)) {
403 if (sAll == 0.0 && cAll == 0.0 && bAll == 0.0) {
404 out = 0.0; // every product and every error is zero
405 return true;
406 }
407 return false;
408 }
409 // Neighbours of the normal double r by stepping its bit pattern; their
410 // distances to r are exact, and so are the halves.
411 const auto rb = std::bit_cast<uint64_t>(r);
412 const double above = std::bit_cast<double>(r > 0.0 ? rb + 1 : rb - 1);
413 const double below = std::bit_cast<double>(r > 0.0 ? rb - 1 : rb + 1);
414 const double hUp = 0.5 * (above - r);
415 const double hDn = 0.5 * (r - below);
416 constexpr double kMargin = 1.0 - 0x1p-30;
417 if (d + E < hUp * kMargin && d - E > -hDn * kMargin) {
418 out = r;
419 return true;
420 }
421 return false;
422}
423
424#if defined(__FMA__) || defined(__ARM_FEATURE_FMA) || defined(_M_ARM64)
425bool certifiedDot(const double *x, const double *y, std::size_t n,
426 double &out) {
427 return certifiedDotT<true>(x, y, n, out);
428}
429#elif (defined(__x86_64__) || defined(__i386__)) && defined(__GNUC__)
430// Baseline x86-64 build: the FMA form runs only after the CPU reports FMA.
431__attribute__((target("fma"))) bool
432certifiedDotFma(const double *x, const double *y, std::size_t n, double &out) {
433 return certifiedDotT<true>(x, y, n, out);
434}
435
436bool certifiedDot(const double *x, const double *y, std::size_t n,
437 double &out) {
438 static const bool hasFma = __builtin_cpu_supports("fma");
439 return hasFma ? certifiedDotFma(x, y, n, out)
440 : certifiedDotT<false>(x, y, n, out);
441}
442#else
443bool certifiedDot(const double *x, const double *y, std::size_t n,
444 double &out) {
445 return certifiedDotT<false>(x, y, n, out);
446}
447#endif
448
449} // namespace
450
451double dot(const double *x, const double *y, std::size_t n) {
452 double certified;
453 if (certifiedDot(x, y, n, certified)) {
454 return certified;
455 }
456 std::array<Accumulator, kLanes> acc;
457 for (std::size_t i = 0; i < n; ++i) {
458 Decoded a;
459 Decoded b;
460 if (!decode(x[i], a) || !decode(y[i], b)) [[unlikely]] {
461 double plain = 0.0;
462 for (std::size_t j = 0; j < n; ++j) {
463 plain += x[j] * y[j];
464 }
465 return plain;
466 }
467 uint64_t hi;
468 uint64_t lo;
469 mul64(a.m, b.m, hi, lo);
470 acc[i % kLanes].add(hi, lo, a.e + b.e, a.neg != b.neg);
471 if ((i + 1) % kNormalizeEvery == 0) [[unlikely]] {
472 for (auto &lane : acc) {
473 lane.fold();
474 }
475 }
476 }
477 for (std::size_t k = 1; k < kLanes; ++k) {
478 acc[0].absorb(acc[k]);
479 }
480 return acc[0].round();
481}
482
483double orderedDot(const double *x, const double *y, std::size_t n) {
484 double l0 = 0.0;
485 double l1 = 0.0;
486 double l2 = 0.0;
487 double l3 = 0.0;
488 std::size_t i = 0;
489 for (; i + 4 <= n; i += 4) {
490 const double p0 = x[i] * y[i];
491 const double p1 = x[i + 1] * y[i + 1];
492 const double p2 = x[i + 2] * y[i + 2];
493 const double p3 = x[i + 3] * y[i + 3];
494 l0 += p0;
495 l1 += p1;
496 l2 += p2;
497 l3 += p3;
498 }
499 if (i < n) {
500 const double p = x[i] * y[i];
501 l0 += p;
502 }
503 if (i + 1 < n) {
504 const double p = x[i + 1] * y[i + 1];
505 l1 += p;
506 }
507 if (i + 2 < n) {
508 const double p = x[i + 2] * y[i + 2];
509 l2 += p;
510 }
511 return (l0 + l1) + (l2 + l3);
512}
513
514double sum(const double *x, std::size_t n) { return sumStrided(x, n, 1); }
515
516double sumStrided(const double *x, std::size_t n, std::size_t stride) {
517 Accumulator acc;
518 for (std::size_t i = 0; i < n; ++i) {
519 Decoded a;
520 if (!decode(x[i * stride], a)) [[unlikely]] {
521 double plain = 0.0;
522 for (std::size_t j = 0; j < n; ++j) {
523 plain += x[j * stride];
524 }
525 return plain;
526 }
527 acc.add(0, a.m, a.e, a.neg);
528 if ((i + 1) % kNormalizeEvery == 0) [[unlikely]] {
529 acc.fold();
530 }
531 }
532 return acc.round();
533}
534
535} // namespace eonc::exact
#define EONC_EXACT_INLINE
constexpr std::size_t kLanes
double sumStrided(const double *x, std::size_t n, std::size_t stride)
Same sum for x[i * stride].
double dot(const double *x, const double *y, std::size_t n)
Round-to-nearest-even value of the exact sum of x[i] * y[i].
double sum(const double *x, std::size_t n)
Round-to-nearest-even value of the exact sum of x[i].
double orderedDot(const double *x, const double *y, std::size_t n)
A fixed summation tree: four lanes (term i goes to lane i mod 4, each lane a left-to-right sum of rou...