Emulate java.util.Int/Long/DoubleSummaryStatistics
Change-Id: I49555fd7f4e60646f36db9185fb56ba9c692b06b
diff --git a/user/super/com/google/gwt/emul/java/util/DoubleSummaryStatistics.java b/user/super/com/google/gwt/emul/java/util/DoubleSummaryStatistics.java
new file mode 100644
index 0000000..5163cdc
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/util/DoubleSummaryStatistics.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package java.util;
+
+import java.util.function.DoubleConsumer;
+
+/**
+ * See <a
+ * href="https://docs.oracle.com/javase/8/docs/api/java/util/DoubleSummaryStatistics.html">the
+ * official Java API doc</a> for details.
+ */
+public class DoubleSummaryStatistics implements DoubleConsumer {
+
+ private long count;
+ private double min = Double.POSITIVE_INFINITY;
+ private double max = Double.NEGATIVE_INFINITY;
+ private double sum;
+ private double sumError;
+ // Because of Kahan summation compensation a naive summation is required
+ // to keep track of infinity values correctly.
+ private double naiveSum;
+
+ @Override
+ public void accept(double value) {
+ count++;
+ min = Math.min(min, value);
+ max = Math.max(max, value);
+ naiveSum += value;
+ sum(value);
+ }
+
+ public void combine(DoubleSummaryStatistics other) {
+ count += other.count;
+ min = Math.min(min, other.min);
+ max = Math.max(max, other.max);
+ naiveSum += other.naiveSum;
+ sum(other.sum);
+ sum(other.sumError);
+ }
+
+ public double getAverage() {
+ return count > 0 ? getSum() / count : 0d;
+ }
+
+ public long getCount() {
+ return count;
+ }
+
+ public double getMin() {
+ return min;
+ }
+
+ public double getMax() {
+ return max;
+ }
+
+ public double getSum() {
+ // Adding sum and sumError here to get a better result
+ // because Kahan summation always applies error compensation
+ // on the next summation.
+ double compensatedSum = sum + sumError;
+ // sumError can be NaN if infinity values had been accepted.
+ if (Double.isNaN(compensatedSum) && Double.isInfinite(naiveSum)) {
+ return naiveSum;
+ }
+ return compensatedSum;
+ }
+
+ @Override
+ public String toString() {
+ return "DoubleSummaryStatistics[" +
+ "count = " + count +
+ ", avg = " + getAverage() +
+ ", min = " + min +
+ ", max = " + max +
+ ", sum = " + getSum() +
+ "]";
+ }
+
+ /**
+ * Adds a new value to the current sum using Kahan summation
+ * algorithm for improved summation precision.
+ *
+ * https://en.wikipedia.org/wiki/Kahan_summation_algorithm
+ *
+ * @param value the value being added to the sum
+ */
+ private void sum(double value) {
+ double compensatedValue = value - sumError;
+ double newSum = sum + compensatedValue;
+ // Logically 'summationError' always evaluates to 0
+ // but in reality it contains a small summation error
+ // that can occur because of rounding in floating point arithmetic.
+ // For example 1.0 + EPSILON with EPSILON being half machine precision
+ // (basically Math.ulp(1.0)/2) will result in 1.0.
+ // Tests should verify that GWT compiler / Closure compiler do not
+ // remove this line during optimizations.
+ // NOTE: sumError can become NaN for infinity values.
+ sumError = (newSum - sum) - compensatedValue;
+ sum = newSum;
+ }
+}
diff --git a/user/super/com/google/gwt/emul/java/util/IntSummaryStatistics.java b/user/super/com/google/gwt/emul/java/util/IntSummaryStatistics.java
new file mode 100644
index 0000000..ca78c82
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/util/IntSummaryStatistics.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package java.util;
+
+import java.util.function.IntConsumer;
+
+/**
+ * See <a
+ * href="https://docs.oracle.com/javase/8/docs/api/java/util/IntSummaryStatistics.html">the
+ * official Java API doc</a> for details.
+ */
+public class IntSummaryStatistics implements IntConsumer {
+
+ private long count;
+ private int min = Integer.MAX_VALUE;
+ private int max = Integer.MIN_VALUE;
+ private long sum;
+
+ @Override
+ public void accept(int value) {
+ count++;
+ min = Math.min(min, value);
+ max = Math.max(max, value);
+ sum += value;
+ }
+
+ public void combine(IntSummaryStatistics other) {
+ count += other.count;
+ min = Math.min(min, other.min);
+ max = Math.max(max, other.max);
+ sum += other.sum;
+ }
+
+ public double getAverage() {
+ return count > 0 ? (double) sum / count : 0d;
+ }
+
+ public long getCount() {
+ return count;
+ }
+
+ public int getMin() {
+ return min;
+ }
+
+ public int getMax() {
+ return max;
+ }
+
+ public long getSum() {
+ return sum;
+ }
+
+ @Override
+ public String toString() {
+ return "IntSummaryStatistics[" +
+ "count = " + count +
+ ", avg = " + getAverage() +
+ ", min = " + min +
+ ", max = " + max +
+ ", sum = " + sum +
+ "]";
+ }
+}
diff --git a/user/super/com/google/gwt/emul/java/util/LongSummaryStatistics.java b/user/super/com/google/gwt/emul/java/util/LongSummaryStatistics.java
new file mode 100644
index 0000000..0794c90
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/util/LongSummaryStatistics.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package java.util;
+
+import java.util.function.IntConsumer;
+import java.util.function.LongConsumer;
+
+/**
+ * See <a
+ * href="https://docs.oracle.com/javase/8/docs/api/java/util/LongSummaryStatistics.html">the
+ * official Java API doc</a> for details.
+ */
+public class LongSummaryStatistics implements LongConsumer, IntConsumer {
+
+ private long count;
+ private long min = Long.MAX_VALUE;
+ private long max = Long.MIN_VALUE;
+ private long sum;
+
+ @Override
+ public void accept(int value) {
+ accept((long) value);
+ }
+
+ @Override
+ public void accept(long value) {
+ count++;
+ min = Math.min(min, value);
+ max = Math.max(max, value);
+ sum += value;
+ }
+
+ public void combine(LongSummaryStatistics other) {
+ count += other.count;
+ min = Math.min(min, other.min);
+ max = Math.max(max, other.max);
+ sum += other.sum;
+ }
+
+ public double getAverage() {
+ return count > 0 ? (double) sum / count : 0d;
+ }
+
+ public long getCount() {
+ return count;
+ }
+
+ public long getMin() {
+ return min;
+ }
+
+ public long getMax() {
+ return max;
+ }
+
+ public long getSum() {
+ return sum;
+ }
+
+ @Override
+ public String toString() {
+ return "LongSummaryStatistics[" +
+ "count = " + count +
+ ", avg = " + getAverage() +
+ ", min = " + min +
+ ", max = " + max +
+ ", sum = " + sum +
+ "]";
+ }
+}
diff --git a/user/test/com/google/gwt/emultest/EmulJava8Suite.java b/user/test/com/google/gwt/emultest/EmulJava8Suite.java
index d713785..09394ae 100644
--- a/user/test/com/google/gwt/emultest/EmulJava8Suite.java
+++ b/user/test/com/google/gwt/emultest/EmulJava8Suite.java
@@ -15,6 +15,9 @@
*/
package com.google.gwt.emultest;
+import com.google.gwt.emultest.java8.util.DoubleSummaryStatisticsTest;
+import com.google.gwt.emultest.java8.util.IntSummaryStatisticsTest;
+import com.google.gwt.emultest.java8.util.LongSummaryStatisticsTest;
import com.google.gwt.emultest.java8.util.OptionalDoubleTest;
import com.google.gwt.emultest.java8.util.OptionalIntTest;
import com.google.gwt.emultest.java8.util.OptionalLongTest;
@@ -40,7 +43,9 @@
suite.addTestSuite(OptionalDoubleTest.class);
suite.addTestSuite(PrimitiveIteratorTest.class);
suite.addTestSuite(StringJoinerTest.class);
-
+ suite.addTestSuite(DoubleSummaryStatisticsTest.class);
+ suite.addTestSuite(IntSummaryStatisticsTest.class);
+ suite.addTestSuite(LongSummaryStatisticsTest.class);
return suite;
}
}
diff --git a/user/test/com/google/gwt/emultest/java8/util/DoubleSummaryStatisticsTest.java b/user/test/com/google/gwt/emultest/java8/util/DoubleSummaryStatisticsTest.java
new file mode 100644
index 0000000..bb4b3b0
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java8/util/DoubleSummaryStatisticsTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.emultest.java8.util;
+
+import com.google.gwt.emultest.java.util.EmulTestBase;
+
+import static java.lang.Double.MAX_VALUE;
+import static java.lang.Double.MIN_VALUE;
+import static java.lang.Double.NEGATIVE_INFINITY;
+import static java.lang.Double.NaN;
+import static java.lang.Double.POSITIVE_INFINITY;
+
+import java.util.Arrays;
+import java.util.DoubleSummaryStatistics;
+import java.util.List;
+
+/**
+ * Tests {@link DoubleSummaryStatistics}.
+ */
+public class DoubleSummaryStatisticsTest extends EmulTestBase {
+
+ private DoubleSummaryStatistics stats;
+
+ @Override
+ protected void gwtSetUp() throws Exception {
+ stats = new DoubleSummaryStatistics();
+ }
+
+ public void testAverageAndSumWithCompensation() throws Exception {
+ assertEquals(0d, stats.getAverage());
+ assertEquals(0d, stats.getSum());
+
+ double initial = 1.0d;
+ long count = 1000000;
+
+ // 'precision' is the hardcoded result of Math.ulp(initial) in JVM,
+ // since GWT does not emulate Math.ulp().
+ // This value represents the distance from 'initial' (1.0d) to the
+ // previous/next double. If we add half or less of that distance/precision
+ // to 'initial' then the result will be truncated to 'initial' again due to
+ // floating point arithmetic rounding.
+ // With Kahan summation such rounding errors are detected and compensated
+ // so the summation result should (nearly) equal the expected sum.
+ double precision = 2.220446049250313E-16;
+ double value = precision / 2;
+ double expectedSum = initial + (count * value);
+ long expectedCount = count + 1;
+ double expectedAverage = expectedSum / expectedCount;
+
+ stats.accept(initial);
+ for (int i = 0; i < count; ++i) {
+ stats.accept(value);
+ }
+
+ // TODO (jnehlmeier): While delta = 0 works we probably want to allow some error?
+ // Or maybe use ulp differences instead of a delta?
+ assertEquals(expectedAverage, stats.getAverage(), 0);
+ assertEquals(expectedSum, stats.getSum(), 0);
+ }
+
+ public void testCombine() throws Exception {
+ stats.accept(1.0d);
+ stats.accept(2.0d);
+
+ DoubleSummaryStatistics otherStats = new DoubleSummaryStatistics();
+ otherStats.accept(3.0d);
+ otherStats.accept(4.0d);
+
+ stats.combine(otherStats);
+
+ assertEquals(2.5d, stats.getAverage());
+ assertEquals(1d, stats.getMin());
+ assertEquals(4d, stats.getMax());
+ assertEquals(10d, stats.getSum());
+ assertEquals(4, stats.getCount());
+ }
+
+ public void testCountMaxMin() {
+ assertEquals(0, stats.getCount());
+ assertEquals(NEGATIVE_INFINITY, stats.getMax());
+ assertEquals(POSITIVE_INFINITY, stats.getMin());
+
+ double[][] testData = {
+ // aDouble, max, min
+ { 1.0, 1.0, 1.0 },
+ { -1.0, 1.0, -1.0 },
+ { 2.5, 2.5, -1.0 },
+ { -2.5, 2.5, -2.5 },
+ { MAX_VALUE, MAX_VALUE, -2.5 },
+ { MIN_VALUE, MAX_VALUE, -2.5 },
+ { POSITIVE_INFINITY, POSITIVE_INFINITY, -2.5 },
+ { NEGATIVE_INFINITY, POSITIVE_INFINITY, NEGATIVE_INFINITY },
+ };
+
+ for (int i = 0; i < testData.length; ++i) {
+ long expectedCount = i + 1;
+ double aDouble = testData[i][0];
+ double expectedMax = testData[i][1];
+ double expectedMin = testData[i][2];
+
+ stats.accept(aDouble);
+
+ assertEquals(expectedCount, stats.getCount());
+ assertEquals(expectedMax, stats.getMax());
+ assertEquals(expectedMin, stats.getMin());
+ }
+ }
+
+ public void testInfinity() {
+ stats.accept(NEGATIVE_INFINITY);
+ stats.accept(NEGATIVE_INFINITY);
+ assertEquals(NEGATIVE_INFINITY, stats.getAverage());
+ assertEquals(NEGATIVE_INFINITY, stats.getMax());
+ assertEquals(NEGATIVE_INFINITY, stats.getMin());
+ assertEquals(NEGATIVE_INFINITY, stats.getSum());
+
+ stats.accept(POSITIVE_INFINITY);
+ assertTrue(Double.isNaN(stats.getAverage()));
+ assertEquals(POSITIVE_INFINITY, stats.getMax());
+ assertEquals(NEGATIVE_INFINITY, stats.getMin());
+ assertTrue(Double.isNaN(stats.getSum()));
+
+ stats = new DoubleSummaryStatistics();
+ stats.accept(POSITIVE_INFINITY);
+ stats.accept(POSITIVE_INFINITY);
+ assertEquals(POSITIVE_INFINITY, stats.getAverage());
+ assertEquals(POSITIVE_INFINITY, stats.getMax());
+ assertEquals(POSITIVE_INFINITY, stats.getMin());
+ assertEquals(POSITIVE_INFINITY, stats.getSum());
+
+ stats.accept(NEGATIVE_INFINITY);
+ assertTrue(Double.isNaN(stats.getAverage()));
+ assertEquals(POSITIVE_INFINITY, stats.getMax());
+ assertEquals(NEGATIVE_INFINITY, stats.getMin());
+ assertTrue(Double.isNaN(stats.getSum()));
+ }
+
+ public void testNaN() {
+ List<Double> testData = Arrays.asList(
+ NaN, -1.5d, 2.5d, MAX_VALUE, MIN_VALUE, NaN,
+ NEGATIVE_INFINITY, POSITIVE_INFINITY);
+
+ stats.accept(5.0d);
+ for (Double aDouble : testData) {
+ stats.accept(aDouble);
+ assertTrue(Double.isNaN(stats.getAverage()));
+ assertTrue(Double.isNaN(stats.getMax()));
+ assertTrue(Double.isNaN(stats.getMin()));
+ assertTrue(Double.isNaN(stats.getSum()));
+ }
+ }
+}
\ No newline at end of file
diff --git a/user/test/com/google/gwt/emultest/java8/util/IntSummaryStatisticsTest.java b/user/test/com/google/gwt/emultest/java8/util/IntSummaryStatisticsTest.java
new file mode 100644
index 0000000..e1a49d2
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java8/util/IntSummaryStatisticsTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.emultest.java8.util;
+
+import com.google.gwt.emultest.java.util.EmulTestBase;
+
+import static java.lang.Integer.MAX_VALUE;
+import static java.lang.Integer.MIN_VALUE;
+
+import java.util.IntSummaryStatistics;
+
+/**
+ * Tests {@link IntSummaryStatistics}.
+ */
+public class IntSummaryStatisticsTest extends EmulTestBase {
+
+ private IntSummaryStatistics stats;
+
+ @Override
+ protected void gwtSetUp() throws Exception {
+ stats = new IntSummaryStatistics();
+ }
+
+ public void testCombine() throws Exception {
+
+ stats.accept(1);
+ stats.accept(2);
+
+ IntSummaryStatistics otherStats = new IntSummaryStatistics();
+ otherStats.accept(3);
+ otherStats.accept(4);
+
+ stats.combine(otherStats);
+
+ assertEquals(2.5d, stats.getAverage());
+ assertEquals(1, stats.getMin());
+ assertEquals(4, stats.getMax());
+ assertEquals(10L, stats.getSum());
+ assertEquals(4L, stats.getCount());
+ }
+
+ public void testStats() {
+
+ assertEquals(0L, stats.getCount());
+ assertEquals(0d, stats.getAverage());
+ assertEquals(MIN_VALUE, stats.getMax());
+ assertEquals(MAX_VALUE, stats.getMin());
+ assertEquals(0L, stats.getSum());
+
+ int[][] testData = {
+ // anInt, max, min, sum
+ { 1, 1, 1, 1 },
+ { -1, 1, -1, 0 },
+ { 2, 2, -1, 2 },
+ { -2, 2, -2, 0 },
+ { MAX_VALUE, MAX_VALUE, -2, MAX_VALUE },
+ { MIN_VALUE, MAX_VALUE, MIN_VALUE, -1 },
+ };
+
+ for (int i = 0; i < testData.length; ++i) {
+ long expectedCount = i + 1;
+ int anInt = testData[i][0];
+ int expectedMax = testData[i][1];
+ int expectedMin = testData[i][2];
+ long expectedSum = testData[i][3];
+ double expectedAverage = expectedSum / (double) expectedCount;
+
+ stats.accept(anInt);
+
+ assertEquals(expectedAverage, stats.getAverage());
+ assertEquals(expectedCount, stats.getCount());
+ assertEquals(expectedMax, stats.getMax());
+ assertEquals(expectedMin, stats.getMin());
+ assertEquals(expectedSum, stats.getSum());
+ }
+ }
+}
\ No newline at end of file
diff --git a/user/test/com/google/gwt/emultest/java8/util/LongSummaryStatisticsTest.java b/user/test/com/google/gwt/emultest/java8/util/LongSummaryStatisticsTest.java
new file mode 100644
index 0000000..518351c
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java8/util/LongSummaryStatisticsTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.emultest.java8.util;
+
+import com.google.gwt.emultest.java.util.EmulTestBase;
+
+import static java.lang.Long.MAX_VALUE;
+import static java.lang.Long.MIN_VALUE;
+
+import java.util.LongSummaryStatistics;
+
+/**
+ * Tests {@link LongSummaryStatistics}.
+ */
+public class LongSummaryStatisticsTest extends EmulTestBase {
+
+ private LongSummaryStatistics stats;
+
+ @Override
+ protected void gwtSetUp() throws Exception {
+ stats = new LongSummaryStatistics();
+ }
+
+ public void testCombine() throws Exception {
+
+ stats.accept(1);
+ stats.accept(2);
+
+ LongSummaryStatistics otherStats = new LongSummaryStatistics();
+ otherStats.accept(3);
+ otherStats.accept(4);
+
+ stats.combine(otherStats);
+
+ assertEquals(2.5d, stats.getAverage());
+ assertEquals(1, stats.getMin());
+ assertEquals(4, stats.getMax());
+ assertEquals(10L, stats.getSum());
+ assertEquals(4L, stats.getCount());
+ }
+
+ public void testStats() {
+
+ assertEquals(0L, stats.getCount());
+ assertEquals(0d, stats.getAverage());
+ assertEquals(MIN_VALUE, stats.getMax());
+ assertEquals(MAX_VALUE, stats.getMin());
+ assertEquals(0L, stats.getSum());
+
+ long[][] testData = {
+ // aLong, max, min, sum
+ { 1, 1, 1, 1 },
+ { -1, 1, -1, 0 },
+ { 2, 2, -1, 2 },
+ { -2, 2, -2, 0 },
+ { MAX_VALUE, MAX_VALUE, -2, MAX_VALUE },
+ { MIN_VALUE, MAX_VALUE, MIN_VALUE, -1 },
+ };
+
+ for (int i = 0; i < testData.length; ++i) {
+ long expectedCount = i + 1;
+ long aLong = testData[i][0];
+ long expectedMax = testData[i][1];
+ long expectedMin = testData[i][2];
+ long expectedSum = testData[i][3];
+ double expectedAverage = expectedSum / (double) expectedCount;
+
+ stats.accept(aLong);
+
+ assertEquals(expectedAverage, stats.getAverage());
+ assertEquals(expectedCount, stats.getCount());
+ assertEquals(expectedMax, stats.getMax());
+ assertEquals(expectedMin, stats.getMin());
+ assertEquals(expectedSum, stats.getSum());
+ }
+ }
+}
\ No newline at end of file