/**
* 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 .
*
* 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
#include
#include
#include "mongo/platform/decimal128.h"
#include "mongo/util/assert_util.h"
namespace mongo {
using DoubleDouble = std::pair;
/**
* 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