Home | History | Annotate | Download | only in stream
      1 /*
      2  * Copyright (c) 2013, 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 import java.util.*;
     25 import java.util.function.*;
     26 import java.util.stream.*;
     27 
     28 import static java.lang.Double.*;
     29 
     30 /*
     31  * @test
     32  * @bug 8006572 8030212
     33  * @summary Test for use of non-naive summation in stream-related sum and average operations.
     34  */
     35 public class TestDoubleSumAverage {
     36     public static void main(String... args) {
     37         int failures = 0;
     38 
     39         failures += testZeroAverageOfNonEmptyStream();
     40         failures += testForCompenstation();
     41         failures += testNonfiniteSum();
     42 
     43         if (failures > 0) {
     44             throw new RuntimeException("Found " + failures + " numerical failure(s).");
     45         }
     46     }
     47 
     48     /**
     49      * Test to verify that a non-empty stream with a zero average is non-empty.
     50      */
     51     private static int testZeroAverageOfNonEmptyStream() {
     52         Supplier<DoubleStream> ds = () -> DoubleStream.iterate(0.0, e -> 0.0).limit(10);
     53 
     54         return  compareUlpDifference(0.0, ds.get().average().getAsDouble(), 0);
     55     }
     56 
     57     /**
     58      * Compute the sum and average of a sequence of double values in
     59      * various ways and report an error if naive summation is used.
     60      */
     61     private static int testForCompenstation() {
     62         int failures = 0;
     63 
     64         /*
     65          * The exact sum of the test stream is 1 + 1e6*ulp(1.0) but a
     66          * naive summation algorithm will return 1.0 since (1.0 +
     67          * ulp(1.0)/2) will round to 1.0 again.
     68          */
     69         double base = 1.0;
     70         double increment = Math.ulp(base)/2.0;
     71         int count = 1_000_001;
     72 
     73         double expectedSum = base + (increment * (count - 1));
     74         double expectedAvg = expectedSum / count;
     75 
     76         // Factory for double a stream of [base, increment, ..., increment] limited to a size of count
     77         Supplier<DoubleStream> ds = () -> DoubleStream.iterate(base, e -> increment).limit(count);
     78 
     79         DoubleSummaryStatistics stats = ds.get().collect(DoubleSummaryStatistics::new,
     80                                                          DoubleSummaryStatistics::accept,
     81                                                          DoubleSummaryStatistics::combine);
     82 
     83         failures += compareUlpDifference(expectedSum, stats.getSum(), 3);
     84         failures += compareUlpDifference(expectedAvg, stats.getAverage(), 3);
     85 
     86         failures += compareUlpDifference(expectedSum,
     87                                          ds.get().sum(), 3);
     88         failures += compareUlpDifference(expectedAvg,
     89                                          ds.get().average().getAsDouble(), 3);
     90 
     91         failures += compareUlpDifference(expectedSum,
     92                                          ds.get().boxed().collect(Collectors.summingDouble(d -> d)), 3);
     93         failures += compareUlpDifference(expectedAvg,
     94                                          ds.get().boxed().collect(Collectors.averagingDouble(d -> d)),3);
     95         return failures;
     96     }
     97 
     98     private static int testNonfiniteSum() {
     99         int failures = 0;
    100 
    101         Map<Supplier<DoubleStream>, Double> testCases = new LinkedHashMap<>();
    102         testCases.put(() -> DoubleStream.of(MAX_VALUE, MAX_VALUE),   POSITIVE_INFINITY);
    103         testCases.put(() -> DoubleStream.of(-MAX_VALUE, -MAX_VALUE), NEGATIVE_INFINITY);
    104 
    105         testCases.put(() -> DoubleStream.of(1.0d, POSITIVE_INFINITY, 1.0d), POSITIVE_INFINITY);
    106         testCases.put(() -> DoubleStream.of(POSITIVE_INFINITY),             POSITIVE_INFINITY);
    107         testCases.put(() -> DoubleStream.of(POSITIVE_INFINITY, POSITIVE_INFINITY), POSITIVE_INFINITY);
    108         testCases.put(() -> DoubleStream.of(POSITIVE_INFINITY, POSITIVE_INFINITY, 0.0), POSITIVE_INFINITY);
    109 
    110         testCases.put(() -> DoubleStream.of(1.0d, NEGATIVE_INFINITY, 1.0d), NEGATIVE_INFINITY);
    111         testCases.put(() -> DoubleStream.of(NEGATIVE_INFINITY),             NEGATIVE_INFINITY);
    112         testCases.put(() -> DoubleStream.of(NEGATIVE_INFINITY, NEGATIVE_INFINITY), NEGATIVE_INFINITY);
    113         testCases.put(() -> DoubleStream.of(NEGATIVE_INFINITY, NEGATIVE_INFINITY, 0.0), NEGATIVE_INFINITY);
    114 
    115         testCases.put(() -> DoubleStream.of(1.0d, NaN, 1.0d),               NaN);
    116         testCases.put(() -> DoubleStream.of(NaN),                           NaN);
    117         testCases.put(() -> DoubleStream.of(1.0d, NEGATIVE_INFINITY, POSITIVE_INFINITY, 1.0d), NaN);
    118         testCases.put(() -> DoubleStream.of(1.0d, POSITIVE_INFINITY, NEGATIVE_INFINITY, 1.0d), NaN);
    119         testCases.put(() -> DoubleStream.of(POSITIVE_INFINITY, NaN), NaN);
    120         testCases.put(() -> DoubleStream.of(NEGATIVE_INFINITY, NaN), NaN);
    121         testCases.put(() -> DoubleStream.of(NaN, POSITIVE_INFINITY), NaN);
    122         testCases.put(() -> DoubleStream.of(NaN, NEGATIVE_INFINITY), NaN);
    123 
    124         for(Map.Entry<Supplier<DoubleStream>, Double> testCase : testCases.entrySet()) {
    125             Supplier<DoubleStream> ds = testCase.getKey();
    126             double expected = testCase.getValue();
    127 
    128             DoubleSummaryStatistics stats = ds.get().collect(DoubleSummaryStatistics::new,
    129                                                              DoubleSummaryStatistics::accept,
    130                                                              DoubleSummaryStatistics::combine);
    131 
    132             failures += compareUlpDifference(expected, stats.getSum(), 0);
    133             failures += compareUlpDifference(expected, stats.getAverage(), 0);
    134 
    135             failures += compareUlpDifference(expected, ds.get().sum(), 0);
    136             failures += compareUlpDifference(expected, ds.get().average().getAsDouble(), 0);
    137 
    138             failures += compareUlpDifference(expected, ds.get().boxed().collect(Collectors.summingDouble(d -> d)), 0);
    139             failures += compareUlpDifference(expected, ds.get().boxed().collect(Collectors.averagingDouble(d -> d)), 0);
    140         }
    141 
    142         return failures;
    143     }
    144 
    145     /**
    146      * Compute the ulp difference of two double values and compare against an error threshold.
    147      */
    148     private static int compareUlpDifference(double expected, double computed, double threshold) {
    149         if (!Double.isFinite(expected)) {
    150             // Handle NaN and infinity cases
    151             if (Double.compare(expected, computed) == 0)
    152                 return 0;
    153             else {
    154                 System.err.printf("Unexpected sum, %g rather than %g.%n",
    155                                   computed, expected);
    156                 return 1;
    157             }
    158         }
    159 
    160         double ulpDifference = Math.abs(expected - computed) / Math.ulp(expected);
    161 
    162         if (ulpDifference > threshold) {
    163             System.err.printf("Numerical summation error too large, %g ulps rather than %g.%n",
    164                               ulpDifference, threshold);
    165             return 1;
    166         } else
    167             return 0;
    168     }
    169 }
    170