8214761: Bug in parallel Kahan summation implementation

Reviewed-by: darcy
This commit is contained in:
Ian Graves 2021-09-03 00:50:11 +00:00
parent 7fff22afe7
commit dd871819a0
5 changed files with 235 additions and 10 deletions

View file

@ -156,7 +156,9 @@ public class DoubleSummaryStatistics implements DoubleConsumer {
count += other.count;
simpleSum += other.simpleSum;
sumWithCompensation(other.sum);
sumWithCompensation(other.sumCompensation);
// Subtract compensation bits
sumWithCompensation(-other.sumCompensation);
min = Math.min(min, other.min);
max = Math.max(max, other.max);
}
@ -241,7 +243,7 @@ public class DoubleSummaryStatistics implements DoubleConsumer {
*/
public final double getSum() {
// Better error bounds to add both terms as the final sum
double tmp = sum + sumCompensation;
double tmp = sum - sumCompensation;
if (Double.isNaN(tmp) && Double.isInfinite(simpleSum))
// If the compensated sum is spuriously NaN from
// accumulating one or more same-signed infinite values,

View file

@ -734,7 +734,8 @@ public final class Collectors {
a[2] += val;},
(a, b) -> { sumWithCompensation(a, b[0]);
a[2] += b[2];
return sumWithCompensation(a, b[1]); },
// Subtract compensation bits
return sumWithCompensation(a, -b[1]); },
a -> computeFinalSum(a),
CH_NOID);
}
@ -765,8 +766,8 @@ public final class Collectors {
* correctly-signed infinity stored in the simple sum.
*/
static double computeFinalSum(double[] summands) {
// Better error bounds to add both terms as the final sum
double tmp = summands[0] + summands[1];
// Final sum with better error bounds subtract second summand as it is negated
double tmp = summands[0] - summands[1];
double simpleSum = summands[summands.length - 1];
if (Double.isNaN(tmp) && Double.isInfinite(simpleSum))
return simpleSum;
@ -840,13 +841,19 @@ public final class Collectors {
/*
* In the arrays allocated for the collect operation, index 0
* holds the high-order bits of the running sum, index 1 holds
* the low-order bits of the sum computed via compensated
* the negated low-order bits of the sum computed via compensated
* summation, and index 2 holds the number of values seen.
*/
return new CollectorImpl<>(
() -> new double[4],
(a, t) -> { double val = mapper.applyAsDouble(t); sumWithCompensation(a, val); a[2]++; a[3]+= val;},
(a, b) -> { sumWithCompensation(a, b[0]); sumWithCompensation(a, b[1]); a[2] += b[2]; a[3] += b[3]; return a; },
(a, b) -> {
sumWithCompensation(a, b[0]);
// Subtract compensation bits
sumWithCompensation(a, -b[1]);
a[2] += b[2]; a[3] += b[3];
return a;
},
a -> (a[2] == 0) ? 0.0d : (computeFinalSum(a) / a[2]),
CH_NOID);
}

View file

@ -442,7 +442,7 @@ abstract class DoublePipeline<E_IN>
/*
* In the arrays allocated for the collect operation, index 0
* holds the high-order bits of the running sum, index 1 holds
* the low-order bits of the sum computed via compensated
* the negated low-order bits of the sum computed via compensated
* summation, and index 2 holds the simple sum used to compute
* the proper result if the stream contains infinite values of
* the same sign.
@ -454,7 +454,8 @@ abstract class DoublePipeline<E_IN>
},
(ll, rr) -> {
Collectors.sumWithCompensation(ll, rr[0]);
Collectors.sumWithCompensation(ll, rr[1]);
// Subtract compensation bits
Collectors.sumWithCompensation(ll, -rr[1]);
ll[2] += rr[2];
});
@ -497,7 +498,8 @@ abstract class DoublePipeline<E_IN>
},
(ll, rr) -> {
Collectors.sumWithCompensation(ll, rr[0]);
Collectors.sumWithCompensation(ll, rr[1]);
// Subtract compensation bits
Collectors.sumWithCompensation(ll, -rr[1]);
ll[2] += rr[2];
ll[3] += rr[3];
});