/** * \file Accumulator.hpp * \brief Header for GeographicLib::Accumulator class * * Copyright (c) Charles Karney (2010-2020) and licensed * under the MIT/X11 License. For more information, see * https://geographiclib.sourceforge.io/ **********************************************************************/ #if !defined(GEOGRAPHICLIB_ACCUMULATOR_HPP) #define GEOGRAPHICLIB_ACCUMULATOR_HPP 1 #include namespace GeographicLib { /** * \brief An accumulator for sums * * This allows many numbers of floating point type \e T to be added together * with twice the normal precision. Thus if \e T is double, the effective * precision of the sum is 106 bits or about 32 decimal places. * * The implementation follows J. R. Shewchuk, * Adaptive Precision * Floating-Point Arithmetic and Fast Robust Geometric Predicates, * Discrete & Computational Geometry 18(3) 305--363 (1997). * * Approximate timings (summing a vector) * - double: 2ns * - Accumulator: 23ns * * In the documentation of the member functions, \e sum stands for the value * currently held in the accumulator. * * Example of use: * \include example-Accumulator.cpp **********************************************************************/ template class GEOGRAPHICLIB_EXPORT Accumulator { private: // _s + _t accumulators for the sum. T _s, _t; // Same as Math::sum, but requires abs(u) >= abs(v). This isn't currently // used. static T fastsum(T u, T v, T& t) { GEOGRAPHICLIB_VOLATILE T s = u + v; GEOGRAPHICLIB_VOLATILE T vp = s - u; t = v - vp; return s; } void Add(T y) { // Here's Shewchuk's solution... T u; // hold exact sum as [s, t, u] // Accumulate starting at least significant end y = Math::sum(y, _t, u); _s = Math::sum(y, _s, _t); // Start is _s, _t decreasing and non-adjacent. Sum is now (s + t + u) // exactly with s, t, u non-adjacent and in decreasing order (except for // possible zeros). The following code tries to normalize the result. // Ideally, we want _s = round(s+t+u) and _u = round(s+t+u - _s). The // following does an approximate job (and maintains the decreasing // non-adjacent property). Here are two "failures" using 3-bit floats: // // Case 1: _s is not equal to round(s+t+u) -- off by 1 ulp // [12, -1] - 8 -> [4, 0, -1] -> [4, -1] = 3 should be [3, 0] = 3 // // Case 2: _s+_t is not as close to s+t+u as it shold be // [64, 5] + 4 -> [64, 8, 1] -> [64, 8] = 72 (off by 1) // should be [80, -7] = 73 (exact) // // "Fixing" these problems is probably not worth the expense. The // representation inevitably leads to small errors in the accumulated // values. The additional errors illustrated here amount to 1 ulp of the // less significant word during each addition to the Accumulator and an // additional possible error of 1 ulp in the reported sum. // // Incidentally, the "ideal" representation described above is not // canonical, because _s = round(_s + _t) may not be true. For example, // with 3-bit floats: // // [128, 16] + 1 -> [160, -16] -- 160 = round(145). // But [160, 0] - 16 -> [128, 16] -- 128 = round(144). // if (_s == 0) // This implies t == 0, _s = u; // so result is u else _t += u; // otherwise just accumulate u to t. } T Sum(T y) const { Accumulator a(*this); a.Add(y); return a._s; } public: /** * Construct from a \e T. This is not declared explicit, so that you can * write Accumulator a = 5;. * * @param[in] y set \e sum = \e y. **********************************************************************/ Accumulator(T y = T(0)) : _s(y), _t(0) { static_assert(!std::numeric_limits::is_integer, "Accumulator type is not floating point"); } /** * Set the accumulator to a number. * * @param[in] y set \e sum = \e y. **********************************************************************/ Accumulator& operator=(T y) { _s = y; _t = 0; return *this; } /** * Return the value held in the accumulator. * * @return \e sum. **********************************************************************/ T operator()() const { return _s; } /** * Return the result of adding a number to \e sum (but don't change \e * sum). * * @param[in] y the number to be added to the sum. * @return \e sum + \e y. **********************************************************************/ T operator()(T y) const { return Sum(y); } /** * Add a number to the accumulator. * * @param[in] y set \e sum += \e y. **********************************************************************/ Accumulator& operator+=(T y) { Add(y); return *this; } /** * Subtract a number from the accumulator. * * @param[in] y set \e sum -= \e y. **********************************************************************/ Accumulator& operator-=(T y) { Add(-y); return *this; } /** * Multiply accumulator by an integer. To avoid loss of accuracy, use only * integers such that \e n × \e T is exactly representable as a \e T * (i.e., ± powers of two). Use \e n = −1 to negate \e sum. * * @param[in] n set \e sum *= \e n. **********************************************************************/ Accumulator& operator*=(int n) { _s *= n; _t *= n; return *this; } /** * Multiply accumulator by a number. The fma (fused multiply and add) * instruction is used (if available) in order to maintain accuracy. * * @param[in] y set \e sum *= \e y. **********************************************************************/ Accumulator& operator*=(T y) { using std::fma; T d = _s; _s *= y; d = fma(y, d, -_s); // the error in the first multiplication _t = fma(y, _t, d); // add error to the second term return *this; } /** * Reduce accumulator to the range [-y/2, y/2]. * * @param[in] y the modulus. **********************************************************************/ Accumulator& remainder(T y) { using std::remainder; _s = remainder(_s, y); Add(0); // This renormalizes the result. return *this; } /** * Test equality of an Accumulator with a number. **********************************************************************/ bool operator==(T y) const { return _s == y; } /** * Test inequality of an Accumulator with a number. **********************************************************************/ bool operator!=(T y) const { return _s != y; } /** * Less operator on an Accumulator and a number. **********************************************************************/ bool operator<(T y) const { return _s < y; } /** * Less or equal operator on an Accumulator and a number. **********************************************************************/ bool operator<=(T y) const { return _s <= y; } /** * Greater operator on an Accumulator and a number. **********************************************************************/ bool operator>(T y) const { return _s > y; } /** * Greater or equal operator on an Accumulator and a number. **********************************************************************/ bool operator>=(T y) const { return _s >= y; } }; } // namespace GeographicLib #endif // GEOGRAPHICLIB_ACCUMULATOR_HPP