summaryrefslogtreecommitdiff
path: root/src/mongo/util/summation.h
blob: e340d13d708488a1922ba263bb3edd6447afdd04 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/**
 *    Copyright (C) 2016 MongoDB Inc.
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *    As a special exception, the copyright holders give permission to link the
 *    code of portions of this program with the OpenSSL library under certain
 *    conditions as described in each individual source file and distribute
 *    linked combinations including the program with the OpenSSL library. You
 *    must comply with the GNU Affero General Public License in all respects
 *    for all of the code used other than as permitted herein. If you modify
 *    file(s) with this exception, you may extend this exception to your
 *    version of the file(s), but you are not obligated to do so. If you do not
 *    wish to do so, delete this exception statement from your version. If you
 *    delete this exception statement from all source files in the program,
 *    then also delete it in the license file.
 */

#pragma once

#include <cmath>
#include <tuple>
#include <utility>

#include "mongo/platform/decimal128.h"
#include "mongo/util/assert_util.h"

namespace mongo {

using DoubleDouble = std::pair<double, double>;

/**
 * Class to accurately sum series of numbers using a 2Sum and Fast2Sum formulas to maintain an
 * unevaluated sum of two numbers: a rounded-to-nearest _sum and an _addend.
 * See Sylvie Boldo, Stef Graillat, Jean-Michel Muller. On the robustness of the 2Sum and Fast2Sum
 * algorithms. 2016. https://hal-ens-lyon.archives-ouvertes.fr/ensl-01310023
 */
class DoubleDoubleSummation {
public:
    /**
     * Adds x to the sum, keeping track of a compensation amount to be subtracted later.
     */
    void addDouble(double x) {
        _special += x;                                 // Keep a simple sum to use in case of NaN
        std::tie(x, _addend) = _fast2Sum(x, _addend);  // Compensated add: _addend tinier than _sum
        std::tie(_sum, x) = _2Sum(_sum, x);            // Compensated add: x maybe larger than _sum
        _addend += x;                                  // Store away lowest part of sum
    }

    /**
     * Adds x to internal sum. Extra precision guarantees that sum is exact, unless intermediate
     * sums exceed a magnitude of 2**106.
     */
    void addLong(long long x);

    /**
     * Adds x to internal sum. Adds as double as that is more efficient.
     */
    void addInt(int x) {
        addDouble(x);
    }

    /**
     * Returns the double nearest to the accumulated sum.
     */
    double getDouble() const {
        return std::isnan(_sum) ? _special : _sum;
    }

    /**
     * Return a pair of double representing the sum, with first being the nearest double and second
     * the amount to add for full precision.
     */
    DoubleDouble getDoubleDouble() const {
        return std::isnan(_sum) ? DoubleDouble{_special, 0.0} : DoubleDouble{_sum, _addend};
    }

    /**
     * The result will generally have about 107 bits of precision, or about 32 decimal digits.
     * Summations of even extremely long series of 32-bit and 64-bit integers should be exact.
     */
    Decimal128 getDecimal() const {
        return std::isnan(_sum) ? Decimal128(_special, Decimal128::kRoundTo34Digits)
                                : Decimal128(_sum, Decimal128::kRoundTo34Digits)
                                      .add(Decimal128(_addend, Decimal128::kRoundTo34Digits));
    }

    /**
     * Returns whether the sum is in range of the 64-bit signed integer long long type.
     */
    bool fitsLong() const;

    /**
     * Returns whether the accumulated sum has a fractional part.
     */
    bool isInteger() const {
        return std::trunc(_sum) == _sum && std::trunc(_addend) == _addend;
    }

    /**
     * Returns result of sum rounded to nearest integer, rounding half-way cases away from zero.
     */
    long long getLong() const;

private:
    /**
     * Assuming |b| <= |a|, returns exact unevaluated sum of a and b, where the first member is the
     * double nearest the sum (ties to even) and the second member is the remainder.
     *
     * T. J. Dekker. A floating-point technique for extending the available precision. Numerische
     * Mathematik, 18(3):224–242, 1971.
     */
    DoubleDouble _fast2Sum(double a, double b) {
        double s = a + b;
        double z = s - a;
        double t = b - z;
        return {s, t};
    }

    /**
     * returns exact unevaluated sum of a and b, where the first member is the double nearest the
     * sum (ties to even) and the second member is the remainder.
     *
     * O. Møller. Quasi double-precision in floating-point addition. BIT, 5:37–50, 1965.
     * D. Knuth. The Art of Computer Programming, vol 2. Addison-Wesley, Reading, MA, 3rd ed, 1998.
     */
    DoubleDouble _2Sum(double a, double b) {
        double s = a + b;
        double aPrime = s - b;
        double bPrime = s - aPrime;
        double deltaA = a - aPrime;
        double deltaB = b - bPrime;
        double t = deltaA + deltaB;
        return {s, t};
    }

    double _sum = 0.0;
    double _addend = 0.0;

    // Simple sum to be returned if _sum is NaN. This addresses infinities turning into NaNs when
    // using compensated addition.
    double _special = 0.0;
};
}  // namespace mongo