summaryrefslogtreecommitdiffstats
path: root/sc/inc/kahan.hxx
diff options
context:
space:
mode:
Diffstat (limited to 'sc/inc/kahan.hxx')
-rw-r--r--sc/inc/kahan.hxx276
1 files changed, 276 insertions, 0 deletions
diff --git a/sc/inc/kahan.hxx b/sc/inc/kahan.hxx
new file mode 100644
index 0000000000..c2560635fb
--- /dev/null
+++ b/sc/inc/kahan.hxx
@@ -0,0 +1,276 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include <rtl/math.hxx>
+#include <cmath>
+
+class KahanSum;
+namespace sc::op
+{
+// Checkout available optimization options.
+// Note that it turned out to be problematic to support CPU-specific code
+// that's not guaranteed to be available on that specific platform (see
+// git commit 2d36e7f5186ba5215f2b228b98c24520bd4f2882). SSE2 is guaranteed on
+// x86_64 and it is our baseline requirement for x86 on Windows, so SSE2 use is
+// hardcoded on those platforms.
+// Whenever we raise baseline to e.g. AVX, this may get
+// replaced with AVX code (get it from mentioned git commit).
+// Do it similarly with other platforms.
+#if defined(X86_64) || (defined(INTEL) && defined(_WIN32))
+#define SC_USE_SSE2 1
+KahanSum executeSSE2(size_t& i, size_t nSize, const double* pCurrent);
+#else
+#define SC_USE_SSE2 0
+#endif
+}
+
+/**
+ * This class provides LO with Kahan summation algorithm
+ * About this algorithm: https://en.wikipedia.org/wiki/Kahan_summation_algorithm
+ * For general purpose software we assume first order error is enough.
+ *
+ * Additionally queue and remember the last recent non-zero value and add it
+ * similar to approxAdd() when obtaining the final result to further eliminate
+ * accuracy errors. (e.g. for the dreaded 0.1 + 0.2 - 0.3 != 0.0)
+ */
+
+class KahanSum
+{
+public:
+ constexpr KahanSum() = default;
+
+ constexpr KahanSum(double x_0)
+ : m_fSum(x_0)
+ {
+ }
+
+ constexpr KahanSum(double x_0, double err_0)
+ : m_fSum(x_0)
+ , m_fError(err_0)
+ {
+ }
+
+ constexpr KahanSum(const KahanSum& fSum) = default;
+
+public:
+ /**
+ * Performs one step of the Neumaier sum of doubles.
+ * Overwrites the summand and error.
+ * T could be double or long double.
+ */
+ template <typename T> static inline void sumNeumaierNormal(T& sum, T& err, const double& value)
+ {
+ T t = sum + value;
+ if (std::abs(sum) >= std::abs(value))
+ err += (sum - t) + value;
+ else
+ err += (value - t) + sum;
+ sum = t;
+ }
+
+ /**
+ * Adds a value to the sum using Kahan summation.
+ * @param x_i
+ */
+ void add(double x_i)
+ {
+ if (x_i == 0.0)
+ return;
+
+ if (!m_fMem)
+ {
+ m_fMem = x_i;
+ return;
+ }
+
+ sumNeumaierNormal(m_fSum, m_fError, m_fMem);
+ m_fMem = x_i;
+ }
+
+ /**
+ * Adds a value to the sum using Kahan summation.
+ * @param fSum
+ */
+ inline void add(const KahanSum& fSum)
+ {
+#if SC_USE_SSE2
+ add(fSum.m_fSum + fSum.m_fError);
+ add(fSum.m_fMem);
+#else
+ // Without SSE2 the sum+compensation value fails badly. Continue
+ // keeping the old though slightly off (see tdf#156985) explicit
+ // addition of the compensation value.
+ double sum = fSum.m_fSum;
+ double err = fSum.m_fError;
+ if (fSum.m_fMem != 0.0)
+ sumNeumaierNormal(sum, err, fSum.m_fMem);
+ add(sum);
+ add(err);
+#endif
+ }
+
+ /**
+ * Substracts a value to the sum using Kahan summation.
+ * @param fSum
+ */
+ inline void subtract(const KahanSum& fSum) { add(-fSum); }
+
+public:
+ constexpr KahanSum operator-() const
+ {
+ KahanSum fKahanSum;
+ fKahanSum.m_fSum = -m_fSum;
+ fKahanSum.m_fError = -m_fError;
+ fKahanSum.m_fMem = -m_fMem;
+ return fKahanSum;
+ }
+
+ constexpr KahanSum& operator=(double fSum)
+ {
+ m_fSum = fSum;
+ m_fError = 0;
+ m_fMem = 0;
+ return *this;
+ }
+
+ constexpr KahanSum& operator=(const KahanSum& fSum) = default;
+
+ inline void operator+=(const KahanSum& fSum) { add(fSum); }
+
+ inline void operator+=(double fSum) { add(fSum); }
+
+ inline void operator-=(const KahanSum& fSum) { subtract(fSum); }
+
+ inline void operator-=(double fSum) { add(-fSum); }
+
+ inline KahanSum operator+(double fSum) const
+ {
+ KahanSum fNSum(*this);
+ fNSum.add(fSum);
+ return fNSum;
+ }
+
+ inline KahanSum operator+(const KahanSum& fSum) const
+ {
+ KahanSum fNSum(*this);
+ fNSum += fSum;
+ return fNSum;
+ }
+
+ inline KahanSum operator-(double fSum) const
+ {
+ KahanSum fNSum(*this);
+ fNSum.add(-fSum);
+ return fNSum;
+ }
+
+ inline KahanSum operator-(const KahanSum& fSum) const
+ {
+ KahanSum fNSum(*this);
+ fNSum -= fSum;
+ return fNSum;
+ }
+
+ /**
+ * In some parts of the code of interpr_.cxx this may be used for
+ * product instead of sum. This operator shall be used for that task.
+ */
+ constexpr void operator*=(double fTimes)
+ {
+ if (m_fMem)
+ {
+ m_fSum = get() * fTimes;
+ m_fMem = 0.0;
+ }
+ else
+ {
+ m_fSum = (m_fSum + m_fError) * fTimes;
+ }
+ m_fError = 0.0;
+ }
+
+ constexpr void operator/=(double fDivides)
+ {
+ if (m_fMem)
+ {
+ m_fSum = get() / fDivides;
+ m_fMem = 0.0;
+ }
+ else
+ {
+ m_fSum = (m_fSum + m_fError) / fDivides;
+ }
+ m_fError = 0.0;
+ }
+
+ inline KahanSum operator*(const KahanSum& fTimes) const { return get() * fTimes.get(); }
+
+ inline KahanSum operator*(double fTimes) const { return get() * fTimes; }
+
+ inline KahanSum operator/(const KahanSum& fDivides) const { return get() / fDivides.get(); }
+
+ inline KahanSum operator/(double fDivides) const { return get() / fDivides; }
+
+ inline bool operator<(const KahanSum& fSum) const { return get() < fSum.get(); }
+
+ inline bool operator<(double fSum) const { return get() < fSum; }
+
+ inline bool operator>(const KahanSum& fSum) const { return get() > fSum.get(); }
+
+ inline bool operator>(double fSum) const { return get() > fSum; }
+
+ inline bool operator<=(const KahanSum& fSum) const { return get() <= fSum.get(); }
+
+ inline bool operator<=(double fSum) const { return get() <= fSum; }
+
+ inline bool operator>=(const KahanSum& fSum) const { return get() >= fSum.get(); }
+
+ inline bool operator>=(double fSum) const { return get() >= fSum; }
+
+ inline bool operator==(const KahanSum& fSum) const { return get() == fSum.get(); }
+
+ inline bool operator!=(const KahanSum& fSum) const { return get() != fSum.get(); }
+
+public:
+ /**
+ * Returns the final sum.
+ * @return final sum
+ */
+ double get() const
+ {
+ const double fTotal = m_fSum + m_fError;
+ if (!m_fMem)
+ return fTotal;
+
+ // Check the same condition as rtl::math::approxAdd() and if true
+ // return 0.0, if false use another Kahan summation adding m_fMem.
+ if (((m_fMem < 0.0 && fTotal > 0.0) || (fTotal < 0.0 && m_fMem > 0.0))
+ && rtl::math::approxEqual(m_fMem, -fTotal))
+ {
+ /* TODO: should we reset all values to zero here for further
+ * summation, or is it better to keep them as they are? */
+ return 0.0;
+ }
+
+ // The actual argument passed to add() here does not matter as long as
+ // it is not 0, m_fMem is not 0 and will be added anyway, see add().
+ const_cast<KahanSum*>(this)->add(m_fMem);
+ const_cast<KahanSum*>(this)->m_fMem = 0.0;
+ return m_fSum + m_fError;
+ }
+
+private:
+ double m_fSum = 0;
+ double m_fError = 0;
+ double m_fMem = 0;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */