Summation Algorithms Overview
Task 5a explores how different summation algorithms handle floating‑point cancellation when summing the vector
const std::vector<double> vec = {1.0, 1.0e16, -1.0e16, -0.5};
whose true sum is 0.5. The code defines an abstract Summator
interface (in Summator.hpp
) with a single method:
virtual double sum(const std::vector<double>& values) = 0;
Five concrete strategies implement this interface:
1. For‑Loop Summator
A straightforward loop that accumulates each element in order:
ForLoopSummator.hpp |
---|
| class ForLoopSummator : public Summator {
public:
double sum(const std::vector<double>& vec) const override {
double s = 0.0;
for (size_t i = 0; i < vec.size(); ++i) {
s += vec[i];
}
return s;
}
};
|
This approach suffers from catastrophic cancellation when a very large value overwhelms small ones.
2. GSL Summator
Delegates to the GNU Scientific Library’s gsl_vector_sum
on a gsl_vector
wrapper. It behaves like the naive loop under the hood and exhibits the same numerical issues.
GSLSummator.hpp |
---|
| class GSLSummator : public Summator {
public:
double sum(const std::vector<double>& vec) const override {
size_t N = vec.size();
gsl_vector* v = gsl_vector_alloc(N);
for (size_t i = 0; i < N; ++i) {
gsl_vector_set(v, i, vec[i]);
}
double s = gsl_vector_sum(v); // Uses GSL's vector-sum routine.
gsl_vector_free(v);
return s;
}
};
|
3. Pairwise Summator
Recursively splits the array in half, sums each half, then adds the two partial sums. This reduces error growth by grouping similar‑magnitude terms:
PairwiseSummator.hpp |
---|
| class PairwiseSummator : public Summator {
public:
double sum(const std::vector<double>& vec) const override {
return pairwiseSum(vec, 0, vec.size());
}
private:
// Recursively sums the elements in vec from index [start, end).
double pairwiseSum(const std::vector<double>& vec, std::size_t start, std::size_t end) const {
std::size_t n = end - start;
if (n == 0)
return 0.0;
if (n == 1)
return vec[start];
// Split the array into two halves.
std::size_t mid = start + n / 2;
double leftSum = pairwiseSum(vec, start, mid);
double rightSum = pairwiseSum(vec, mid, end);
return leftSum + rightSum;
}
};
|
4. Kahan Summator
Implements the Kahan compensated summation algorithm, maintaining a separate compensation c
to capture lost low‑order bits:
KahanSummator.hpp |
---|
| class KahanSummator : public Summator {
public:
double sum(const std::vector<double>& vec) const override {
double sum = 0.0;
double c = 0.0; // Compensation for lost low-order bits.
for (size_t i = 0; i < vec.size(); ++i) {
double y = vec[i] - c;
double t = sum + y;
c = (t - sum) - y;
sum = t;
}
return sum;
}
};
|
5. Neumaier Summator
An improved Kahan variant that handles the case where the new term has larger magnitude than the running sum:
NeumaierSummator.hpp |
---|
| class NeumaierSummator : public Summator {
public:
double sum(const std::vector<double>& vec) const override {
double sum = 0.0;
double c = 0.0; // Running compensation for lost low-order bits.
for (size_t i = 0; i < vec.size(); ++i) {
double t = sum + vec[i];
if (std::fabs(sum) >= std::fabs(vec[i])) {
c += (sum - t) + vec[i];
} else {
c += (vec[i] - t) + sum;
}
sum = t;
}
return sum + c; // Apply the correction once at the end.
}
};
|