1+ /*
2+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
3+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+ *
5+ * This code is free software; you can redistribute it and/or modify it
6+ * under the terms of the GNU General Public License version 2 only, as
7+ * published by the Free Software Foundation.
8+ *
9+ * This code is distributed in the hope that it will be useful, but WITHOUT
10+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+ * version 2 for more details (a copy is included in the LICENSE file that
13+ * accompanied this code).
14+ *
15+ * You should have received a copy of the GNU General Public License version
16+ * 2 along with this work; if not, write to the Free Software Foundation,
17+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+ *
19+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+ * or visit www.oracle.com if you need additional information or have any
21+ * questions.
22+ */
23+
24+ /*
25+ * @test
26+ * @bug 8214761
27+ * @run testng CompensatedSums
28+ * @summary
29+ */
30+
31+ import java .util .Random ;
32+ import java .util .function .BiConsumer ;
33+ import java .util .function .ObjDoubleConsumer ;
34+ import java .util .function .Supplier ;
35+ import java .util .stream .Collectors ;
36+ import java .util .stream .DoubleStream ;
37+
38+ import static org .testng .Assert .assertTrue ;
39+
40+ import org .testng .Assert ;
41+ import org .testng .annotations .Test ;
42+
43+ public class CompensatedSums {
44+
45+ @ Test
46+ public void testCompensatedSums () {
47+ double naive = 0 ;
48+ double jdkSequentialStreamError = 0 ;
49+ double goodSequentialStreamError = 0 ;
50+ double jdkParallelStreamError = 0 ;
51+ double goodParallelStreamError = 0 ;
52+ double badParallelStreamError = 0 ;
53+
54+ for (int loop = 0 ; loop < 100 ; loop ++) {
55+ // sequence of random numbers of varying magnitudes, both positive and negative
56+ double [] rand = new Random ().doubles (1_000_000 )
57+ .map (Math ::log )
58+ .map (x -> (Double .doubleToLongBits (x ) % 2 == 0 ) ? x : -x )
59+ .toArray ();
60+
61+ // base case: standard Kahan summation
62+ double [] sum = new double [2 ];
63+ for (int i =0 ; i < rand .length ; i ++) {
64+ sumWithCompensation (sum , rand [i ]);
65+ }
66+
67+ // All error is the squared difference of the standard Kahan Sum vs JDK Stream sum implementation
68+ // Older less accurate implementations included here as the baseline.
69+
70+ // squared error of naive sum by reduction - should be large
71+ naive += Math .pow (DoubleStream .of (rand ).reduce ((x , y ) -> x +y ).getAsDouble () - sum [0 ], 2 );
72+
73+ // squared error of sequential sum - should be 0
74+ jdkSequentialStreamError += Math .pow (DoubleStream .of (rand ).sum () - sum [0 ], 2 );
75+
76+ goodSequentialStreamError += Math .pow (computeFinalSum (DoubleStream .of (rand ).collect (doubleSupplier ,objDoubleConsumer ,goodCollectorConsumer )) - sum [0 ], 2 );
77+
78+ // squared error of parallel sum from the JDK
79+ jdkParallelStreamError += Math .pow (DoubleStream .of (rand ).parallel ().sum () - sum [0 ], 2 );
80+
81+ // squared error of parallel sum
82+ goodParallelStreamError += Math .pow (computeFinalSum (DoubleStream .of (rand ).parallel ().collect (doubleSupplier ,objDoubleConsumer ,goodCollectorConsumer )) - sum [0 ], 2 );
83+
84+ // the bad parallel stream
85+ badParallelStreamError += Math .pow (computeFinalSum (DoubleStream .of (rand ).parallel ().collect (doubleSupplier ,objDoubleConsumer ,badCollectorConsumer )) - sum [0 ], 2 );
86+
87+
88+ }
89+
90+ Assert .assertEquals (goodSequentialStreamError , 0.0 );
91+ Assert .assertEquals (goodSequentialStreamError , jdkSequentialStreamError );
92+
93+ Assert .assertTrue (jdkParallelStreamError <= goodParallelStreamError );
94+ Assert .assertTrue (badParallelStreamError > goodParallelStreamError );
95+
96+ Assert .assertTrue (naive > jdkSequentialStreamError );
97+ Assert .assertTrue (naive > jdkParallelStreamError );
98+
99+ }
100+
101+ // from OpenJDK8 Collectors, unmodified
102+ static double [] sumWithCompensation (double [] intermediateSum , double value ) {
103+ double tmp = value - intermediateSum [1 ];
104+ double sum = intermediateSum [0 ];
105+ double velvel = sum + tmp ; // Little wolf of rounding error
106+ intermediateSum [1 ] = (velvel - sum ) - tmp ;
107+ intermediateSum [0 ] = velvel ;
108+ return intermediateSum ;
109+ }
110+
111+ // from OpenJDK8 Collectors, unmodified
112+ static double computeFinalSum (double [] summands ) {
113+ double tmp = summands [0 ] + summands [1 ];
114+ double simpleSum = summands [summands .length - 1 ];
115+ if (Double .isNaN (tmp ) && Double .isInfinite (simpleSum ))
116+ return simpleSum ;
117+ else
118+ return tmp ;
119+ }
120+
121+ //Suppliers and consumers for Double Stream summation collection.
122+ static Supplier <double []> doubleSupplier = () -> new double [3 ];
123+ static ObjDoubleConsumer <double []> objDoubleConsumer = (double [] ll , double d ) -> {
124+ sumWithCompensation (ll , d );
125+ ll [2 ] += d ;
126+ };
127+ static BiConsumer <double [], double []> badCollectorConsumer =
128+ (ll , rr ) -> {
129+ sumWithCompensation (ll , rr [0 ]);
130+ sumWithCompensation (ll , rr [1 ]);
131+ ll [2 ] += rr [2 ];
132+ };
133+
134+ static BiConsumer <double [], double []> goodCollectorConsumer =
135+ (ll , rr ) -> {
136+ sumWithCompensation (ll , rr [0 ]);
137+ sumWithCompensation (ll , -rr [1 ]);
138+ ll [2 ] += rr [2 ];
139+ };
140+
141+ }
0 commit comments