changeset 7640:51c110b5fc26

Streams.doubleRange with a double range spliterator that ensures the same values are always produced at the same absolute positions regardless of how the spltierator is split and traversed.
author psandoz
date Thu, 14 Mar 2013 12:09:10 +0100
parents c61c4cfa4bd2
children 76a06a20617f
files src/share/classes/java/util/stream/Streams.java test-ng/bootlib/java/util/stream/DoubleStreamTestDataProvider.java test-ng/tests/org/openjdk/tests/java/util/stream/RangeTest.java
diffstat 3 files changed, 505 insertions(+), 136 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/java/util/stream/Streams.java	Thu Mar 14 11:55:05 2013 +0100
+++ b/src/share/classes/java/util/stream/Streams.java	Thu Mar 14 12:09:10 2013 +0100
@@ -28,6 +28,7 @@
 import java.util.Spliterators;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
+import java.util.function.DoubleConsumer;
 import java.util.function.DoubleSupplier;
 import java.util.function.DoubleUnaryOperator;
 import java.util.function.IntConsumer;
@@ -966,6 +967,98 @@
                 Spliterator.IMMUTABLE | Spliterator.NONNULL));
     }
 
+    /**
+     * Creates a sequential {@code DoubleStream} of {@code double} elements from
+     * {@code start} (inclusive) to {@code end} (exclusive) by an incremental or
+     * decremental step of 1.0.
+     * <p>
+     * @implSpec
+     * The implementation behaves as if:
+     * <pre>
+     *     doubleRange(start, end, start &lt;= end ? 1.0 : -1.0);
+     * </pre>
+     *
+     * @param start the (inclusive) initial value
+     * @param end the exclusive upper bound
+     * @return A sequential {@code DoubleStream} for a range of {@code double}
+     *         elements
+     */
+    public static DoubleStream doubleRange(double start, double end) {
+        return doubleRange(start, end, start <= end ? 1.0 : -1.0);
+    }
+
+    /**
+     * Creates a sequential {@code DoubleStream} of {@code double} elements from
+     * {@code start} (inclusive) to {@code end} (exclusive) by {@code step}.
+     * <p>
+     * If {@code start} is less than {@code end} and  {@code step} is greater
+     * than 0 then a stream of increasing values is returned.  If {@code start}
+     * is greater than {@code end} and {@code step} is less than 0 then a stream
+     * of decreasing values is returned. If {@code start} is equal to
+     * {@code end} then an empty stream is returned.
+     * <p>
+     * The size of the stream is {@code Math.ceil((start - end) / step)}.
+     * <p>
+     * An equivalent sequence of increasing values can be produced,
+     * sequentially, using a {@code for} loop as follows:
+     * <pre>
+     *     long size = (long) Math.ceil((start - end) / step);
+     *     long i = 0
+     *     for (double v = start; i < size; i++, v = start + step * i) {
+     *         ...
+     *     }
+     * </pre>
+     * A stream of equivalent values can be produced as follows:
+     * <pre>
+     *     long size = (long) Math.ceil((start - end) / step);
+     *     DoubleStream ds = Streams.longStream(0, size).doubles().
+     *         map(i -> start + step * i);
+     * </pre>
+     *
+     * @param start the (inclusive) initial value
+     * @param end the exclusive upper bound
+     * @param step the difference between consecutive values
+     * @return A sequential {@code DoubleStream} for a range of {@code double}
+     *         elements
+     * @throws IllegalArgumentException
+     *         if {@code start} is greater than {@code end} and {@code step} is
+     *         greater than 0, or if {@code start} is less than {@code end} and
+     *         {@code step} is less than 0, or {@code step} is equal to 0,
+     *         or the size is greater than {@code Long.MAX_VALUE} or is
+     *         {@code NaN}.
+     */
+    public static DoubleStream doubleRange(double start, double end, double step) {
+        // @@@ Need to check for ranges that may not produce distinct values
+        //     such as when the step is very small
+        //     Also clarify the size of the range which may produce more or less
+        //     than expected
+        if (step > 0) {
+            // Decreasing range with an increasing step value
+            if (start > end) throw new IllegalArgumentException(
+                    String.format("Illegal range: start(%f) > end(%f), step(%f) > 0", start, end, step));
+        }
+        else if (step < 0) {
+            // Increasing range with a decreasing step value
+            if (start < end) throw new IllegalArgumentException(
+                    String.format("Illegal range: start(%f) < end(%f), step(%f) < 0", start, end, step));
+        }
+        else if (step == 0) {
+            throw new IllegalArgumentException("Illegal range: step(0)");
+        }
+
+        double size = Math.ceil((end - start) / step);
+        if (Double.isNaN(size)) {
+            throw new IllegalArgumentException(
+                    String.format("Illegal range: size is NaN", size));
+        }
+        else if (size > Long.MAX_VALUE) {
+            throw new IllegalArgumentException(
+                    String.format("Illegal range: size %f > Long.MAX_VALUE", size));
+        }
+
+        return doubleStream(new RangeDoubleSpliterator(start, end, step, 0, (long) size));
+    }
+
     // Stream combining functions
 
     /**
@@ -1145,6 +1238,9 @@
         interface OfDouble extends InfiniteIterator<Double>, PrimitiveIterator.OfDouble { }
     }
 
+    /**
+     * An {@code int} range spliterator.
+     */
     private static final class RangeIntSpliterator implements Spliterator.OfInt {
         private int from;
         private final int upTo;
@@ -1212,6 +1308,9 @@
         }
     }
 
+    /**
+     * A {@code long} range spliterator.
+     */
     private static final class RangeLongSpliterator implements Spliterator.OfLong {
         private long from;
         private final long upTo;
@@ -1279,4 +1378,79 @@
         }
     }
 
+    /**
+     * A {@code double} range spliterator.
+     * <p>
+     * The traversing and splitting logic is equivalent to that of
+     * {@code RangeLongSpliterator} for increasing values with a {@code step} of
+     * {@code 1}.
+     * <p>
+     * A {@code double} value is calculated from the function
+     * {@code start + i * step} where {@code i} is the absolute position of the
+     * value when traversing an instance of this class that has not been split.
+     * This ensures the same values are produced at the same absolute positions
+     * regardless of how an instance of this class is split or traversed.
+     */
+    private static final class RangeDoubleSpliterator implements Spliterator.OfDouble {
+        private final double from;
+        private final double upTo;
+        private final double step;
+
+        private long lFrom;
+        private final long lUpTo;
+
+        RangeDoubleSpliterator(double from, double upTo, double step, long lFrom, long lUpTo) {
+            this.from = from;
+            this.upTo = upTo;
+            this.step = step;
+            this.lFrom = lFrom;
+            this.lUpTo = lUpTo;
+        }
+
+        @Override
+        public boolean tryAdvance(DoubleConsumer consumer) {
+            boolean hasNext = lFrom < lUpTo;
+            if (hasNext) {
+                consumer.accept(from + lFrom * step);
+                lFrom++;
+            }
+            return hasNext;
+        }
+
+        @Override
+        public void forEach(DoubleConsumer consumer) {
+            double hOrigin = from;
+            double hStep = step;
+            long hLUpTo = lUpTo;
+            long i = lFrom;
+            for (; i < hLUpTo; i++) {
+                consumer.accept(hOrigin + i * hStep);
+            }
+            lFrom = i;
+        }
+
+        @Override
+        public long estimateSize() {
+            return lUpTo - lFrom;
+        }
+
+        @Override
+        public int characteristics() {
+            return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED |
+                   Spliterator.IMMUTABLE | Spliterator.NONNULL |
+                   Spliterator.DISTINCT | (step > 0 ? Spliterator.SORTED : 0);
+        }
+
+        @Override
+        public Spliterator.OfDouble trySplit() {
+            return estimateSize() <= 1
+                   ? null
+                   : new RangeDoubleSpliterator(from, upTo, step, lFrom, lFrom = lFrom + midPoint());
+        }
+
+        private long midPoint() {
+            // Size is known to be >= 2
+            return (lUpTo - lFrom) / 2;
+        }
+    }
 }
--- a/test-ng/bootlib/java/util/stream/DoubleStreamTestDataProvider.java	Thu Mar 14 11:55:05 2013 +0100
+++ b/test-ng/bootlib/java/util/stream/DoubleStreamTestDataProvider.java	Thu Mar 14 12:09:10 2013 +0100
@@ -94,15 +94,14 @@
                 list.add(new Object[]{"SpinedList:" + name, 
                         new DoubleStreamTestData.IterableData<>("SpinedList:" + name, isl, () -> (int) isl.count())});
 
-                // @@@ Change to doubleRange when implemented
                 list.add(streamDataDescr("Primitives.range(0,l): " + doubles.length,
-                                         () -> Streams.longRange(0, doubles.length).doubles()));
+                                         () -> Streams.doubleRange(0, doubles.length)));
                 list.add(streamDataDescr("Primitives.range(0,l,2): " + doubles.length,
-                                         () -> Streams.longRange(0, doubles.length, 2).doubles()));
+                                         () -> Streams.doubleRange(0, doubles.length, 2)));
                 list.add(streamDataDescr("Primitives.range(0,l,3): " + doubles.length,
-                                         () -> Streams.longRange(0, doubles.length, 3).doubles()));
+                                         () -> Streams.doubleRange(0, doubles.length, 3)));
                 list.add(streamDataDescr("Primitives.range(0,l,7): " + doubles.length,
-                                         () -> Streams.longRange(0, doubles.length, 7).doubles()));
+                                         () -> Streams.doubleRange(0, doubles.length, 7)));
             }
             testData = list.toArray(new Object[0][]);
         }
@@ -131,15 +130,14 @@
                 spliterators.add(splitDescr("Primitives.s(SpinedBuffer.iterator()):" + name,
                                             () -> Spliterators.spliteratorUnknownSize(isl.iterator(), 0)));
 
-                // @@@ Change to doubleRange when implemented
                 spliterators.add(splitDescr("Primitives.range(0,l):" + name,
-                                            () -> Streams.longRange(0, doubles.length).doubles().spliterator()));
+                                            () -> Streams.doubleRange(0, doubles.length).spliterator()));
                 spliterators.add(splitDescr("Primitives.range(0,l,2):" + name,
-                                            () -> Streams.longRange(0, doubles.length, 2).doubles().spliterator()));
+                                            () -> Streams.doubleRange(0, doubles.length, 2).spliterator()));
                 spliterators.add(splitDescr("Primitives.range(0,l,3):" + name,
-                                            () -> Streams.longRange(0, doubles.length, 3).doubles().spliterator()));
+                                            () -> Streams.doubleRange(0, doubles.length, 3).spliterator()));
                 spliterators.add(splitDescr("Primitives.range(0,l,7):" + name,
-                                            () -> Streams.longRange(0, doubles.length, 7).doubles().spliterator()));
+                                            () -> Streams.doubleRange(0, doubles.length, 7).spliterator()));
                 // Need more!
             }
             spliteratorTestData = spliterators.toArray(new Object[0][]);
--- a/test-ng/tests/org/openjdk/tests/java/util/stream/RangeTest.java	Thu Mar 14 11:55:05 2013 +0100
+++ b/test-ng/tests/org/openjdk/tests/java/util/stream/RangeTest.java	Thu Mar 14 12:09:10 2013 +0100
@@ -26,6 +26,9 @@
 
 import java.util.Arrays;
 import java.util.Optional;
+import java.util.stream.DoubleStreamTestData;
+import java.util.stream.IntStreamTestData;
+import java.util.stream.LongStreamTestData;
 import java.util.stream.OpTestCase;
 import java.util.stream.StreamTestData;
 import java.util.stream.Streams;
@@ -51,173 +54,345 @@
     //
 
     public void testIntRangeErrors() {
-        executeAndCatch(() -> Streams.intRange(1, 1, 0));
-        executeAndCatch(() -> Streams.intRange(1, 10, 0));
-        executeAndCatch(() -> Streams.intRange(10, 1, 0));
-        executeAndCatch(() -> Streams.intRange(-10, -1, 0));
-        executeAndCatch(() -> Streams.intRange(-1, -10, 0));
-        executeAndCatch(() -> Streams.intRange(-10, 10, 0));
-        executeAndCatch(() -> Streams.intRange(10, -10, 0));
-
-        executeAndCatch(() -> Streams.intRange(1, 10, -1));
-        executeAndCatch(() -> Streams.intRange(10, 1, 1));
-
-        executeAndCatch(() -> Streams.intRange(-10, -1, -1));
-        executeAndCatch(() -> Streams.intRange(-1, -10, 1));
-
-        executeAndCatch(() -> Streams.intRange(-10, 10, -1));
-        executeAndCatch(() -> Streams.intRange(10, -10, 1));
+        for (int start : Arrays.asList(1, 10, -1, -10)) {
+            for (int end : Arrays.asList(1, 10, -1, -10)) {
+                for (int step : Arrays.asList(0, 1, -1, Integer.MAX_VALUE, Integer.MIN_VALUE)) {
+                    if ((start < end && step > 0) ||
+                        (start > end && step < 0) ||
+                        (start == end && step != 0)) {
+                        executeAndNoCatch(() -> Streams.intRange(start, end, step));
+                    }
+                    else {
+                        executeAndCatch(() -> Streams.intRange(start, end, step));
+                    }
+                }
+            }
+        }
     }
 
     public void testIntRange() {
         // Without step
-        {
-            int[] inc = Streams.intRange(10, 10).toArray();
-            assertEquals(inc.length, 0);
-            int[] dec = Streams.intRange(10, 10).toArray();
-            assertEquals(dec.length, 0);
-        }
+        for (int start : Arrays.asList(1, 10, -1, -10)) {
+            for (int end : Arrays.asList(1, 10, -1, -10)) {
+                int step = (start < end) ? 1 : -1;
+                int size = Math.abs(start - end);
+                int[] exp = new int[size];
+                if (start < end) {
+                    for (int i = start, p = 0; i < end; i++, p++) {
+                        exp[p] = i;
+                    }
+                } else {
+                    for (int i = start, p = 0; i > end; i--, p++) {
+                        exp[p] = i;
+                    }
+                }
 
-        {
-            int[] inc = Streams.intRange(1, 10).toArray();
-            int[] dec = Streams.intRange(9, 0).toArray();
-            assertTrue(Arrays.equals(reverse(dec), inc));
-        }
+                int[] inc = Streams.intRange(start, end).toArray();
+                assertEquals(inc.length, size);
+                assertTrue(Arrays.equals(exp, inc));
 
-        {
-            int[] inc = Streams.intRange(-10, -1).toArray();
-            int[] dec = Streams.intRange(-2, -11).toArray();
-            assertTrue(Arrays.equals(reverse(dec), inc));
-        }
+                if (start != end) {
+                    int[] dec = Streams.intRange(end - step, start - step).toArray();
+                    assertTrue(Arrays.equals(reverse(dec), inc));
+                }
 
-        {
-            int[] inc = Streams.intRange(10, -10).toArray();
-            int[] dec = Streams.intRange(-9, 11).toArray();
-            assertTrue(Arrays.equals(reverse(dec), inc));
+                withData(intRangeData(start, end, step)).stream(s -> s).
+                        expectedResult(exp).exercise();
+            }
         }
 
         // With step
+        for (int start : Arrays.asList(1, 10, -1, -10)) {
+            for (int end : Arrays.asList(1, 10, -1, -10)) {
+                for (int step : Arrays.asList(1, -1, -2, 2)) {
+                    if ((start < end && step > 0) ||
+                        (start > end && step < 0) ||
+                        (start == end && step != 0)) {
 
-        {
-            int[] inc = Streams.intRange(10, 10, 1).toArray();
-            assertEquals(inc.length, 0);
-            int[] dec = Streams.intRange(10, 10, -1).toArray();
-            assertEquals(dec.length, 0);
-        }
+                        int d = end - start;
+                        int size = (d / step) + ((d % step == 0) ? 0 : 1);
+                        int[] exp = new int[size];
+                        if (start < end) {
+                            for (int i = start, p = 0; i < end; i += step, p++) {
+                                exp[p] = i;
+                            }
+                        } else {
+                            for (int i = start, p = 0; i > end; i += step, p++) {
+                                exp[p] = i;
+                            }
+                        }
 
-        {
-            int[] inc = Streams.intRange(2, 10, 2).toArray();
-            int[] dec = Streams.intRange(8, 0, -2).toArray();
-            assertTrue(Arrays.equals(reverse(dec), inc));
-        }
+                        int[] inc = Streams.intRange(start, end, step).toArray();
+                        assertEquals(inc.length, size);
+                        assertTrue(Arrays.equals(exp, inc));
 
-        {
-            int[] inc = Streams.intRange(-10, -2, 2).toArray();
-            int[] dec = Streams.intRange(-4, -12, -2).toArray();
-            assertTrue(Arrays.equals(reverse(dec), inc));
-        }
+                        if (start != end) {
+                            int s = (start < end) ? 1 : -1;
+                            int[] dec = Streams.intRange(inc[inc.length - 1], inc[0] - s, -step).toArray();
+                            assertTrue(Arrays.equals(reverse(dec), inc));
+                        }
 
-        {
-            int[] inc = Streams.intRange(-10, 10, 2).toArray();
-            int[] dec = Streams.intRange(8, -12, -2).toArray();
-            assertTrue(Arrays.equals(reverse(dec), inc));
+                        withData(intRangeData(start, end, step)).stream(s -> s).
+                                expectedResult(exp).exercise();
+                    }
+                }
+            }
         }
     }
 
+    IntStreamTestData.IntStreamSupplierData intRangeData(int start, int end, int step) {
+        return new IntStreamTestData.IntStreamSupplierData(
+                "int range", () -> Streams.intRange(start, end, step));
+    }
+
     public void tesIntRangeReduce() {
-        int sum = Streams.intRange(0, 100000).reduce(0, Integer::sum);
-        assertEquals(sum, Streams.intRange(0, 100000).parallel().reduce(0, Integer::sum));
+        withData(intRangeData(0, 10000, 1)).
+                terminal(s -> s.reduce(0, Integer::sum)).exercise();
     }
 
     public void testIntInfiniteRangeLimit() {
-        int sum = Streams.iterateInt(0, i -> i + 1).limit(10000).reduce(0, Integer::sum);
-        assertEquals(sum, Streams.iterateInt(0, i -> i + 1).parallel().limit(10000).reduce(0, Integer::sum));
+        withData(new IntStreamTestData.IntStreamSupplierData(
+                "int range", () -> Streams.iterateInt(0, i -> i + 1).limit(10000))).
+                terminal(s -> s.reduce(0, Integer::sum)).exercise();
+    }
+
+    public void testIntInfiniteRangeFindFirst() {
+        int first = Streams.iterateInt(0, i -> i + 1).filter(i -> i > 10000).findFirst().getAsInt();
+        assertEquals(first, Streams.iterateInt(0, i -> i + 1).parallel().filter(i -> i > 10000).findFirst().getAsInt());
     }
 
     //
 
     public void testLongRangeErrors() {
-        executeAndCatch(() -> Streams.longRange(1, 1, 0));
-        executeAndCatch(() -> Streams.longRange(1, 10, 0));
-        executeAndCatch(() -> Streams.longRange(10, 1, 0));
-        executeAndCatch(() -> Streams.longRange(-10, -1, 0));
-        executeAndCatch(() -> Streams.longRange(-1, -10, 0));
-        executeAndCatch(() -> Streams.longRange(-10, 10, 0));
-        executeAndCatch(() -> Streams.longRange(10, -10, 0));
-
-        executeAndCatch(() -> Streams.longRange(1, 10, -1));
-        executeAndCatch(() -> Streams.longRange(10, 1, 1));
-
-        executeAndCatch(() -> Streams.longRange(-10, -1, -1));
-        executeAndCatch(() -> Streams.longRange(-1, -10, 1));
-
-        executeAndCatch(() -> Streams.longRange(-10, 10, -1));
-        executeAndCatch(() -> Streams.longRange(10, -10, 1));
+        for (long start : Arrays.asList(1, 10, -1, -10)) {
+            for (long end : Arrays.asList(1, 10, -1, -10)) {
+                for (long step : Arrays.asList(0L, 1L, -1L, Long.MAX_VALUE, Long.MIN_VALUE)) {
+                    if ((start < end && step > 0) ||
+                        (start > end && step < 0) ||
+                        (start == end && step != 0)) {
+                        executeAndNoCatch(() -> Streams.longRange(start, end, step));
+                    }
+                    else {
+                        executeAndCatch(() -> Streams.longRange(start, end, step));
+                    }
+                }
+            }
+        }
     }
 
     public void testLongRange() {
         // Without step
-        {
-            long[] inc = Streams.longRange(10, 10).toArray();
-            assertEquals(inc.length, 0);
-            long[] dec = Streams.longRange(10, 10).toArray();
-            assertEquals(dec.length, 0);
-        }
+        for (long start : Arrays.asList(1, 1000, -1, -1000)) {
+            for (long end : Arrays.asList(1, 1000, -1, -1000)) {
+                long step = (start < end) ? 1 : -1;
+                long size = Math.abs(start - end);
+                long[] exp = new long[(int) size];
+                if (start < end) {
+                    for (long i = start, p = 0; i < end; i++, p++) {
+                        exp[(int) p] = i;
+                    }
+                } else {
+                    for (long i = start, p = 0; i > end; i--, p++) {
+                        exp[(int) p] = i;
+                    }
+                }
 
-        {
-            long[] inc = Streams.longRange(1, 10).toArray();
-            long[] dec = Streams.longRange(9, 0).toArray();
-            assertTrue(Arrays.equals(reverse(dec), inc));
-        }
+                long[] inc = Streams.longRange(start, end).toArray();
+                assertEquals(inc.length, size);
+                assertTrue(Arrays.equals(exp, inc));
 
-        {
-            long[] inc = Streams.longRange(-10, -1).toArray();
-            long[] dec = Streams.longRange(-2, -11).toArray();
-            assertTrue(Arrays.equals(reverse(dec), inc));
-        }
+                if (start != end) {
+                    long[] dec = Streams.longRange(end - step, start - step).toArray();
+                    assertTrue(Arrays.equals(reverse(dec), inc));
+                }
 
-        {
-            long[] inc = Streams.longRange(10, -10).toArray();
-            long[] dec = Streams.longRange(-9, 11).toArray();
-            assertTrue(Arrays.equals(reverse(dec), inc));
+                withData(longRangeData(start, end, step)).stream(s -> s).
+                        expectedResult(exp).exercise();
+            }
         }
 
         // With step
+        for (long start : Arrays.asList(1, 1000, -1, -1000)) {
+            for (long end : Arrays.asList(1, 1000, -1, -1000)) {
+                for (long step : Arrays.asList(1, -1, -2, 2)) {
+                    if ((start < end && step > 0) ||
+                        (start > end && step < 0) ||
+                        (start == end && step != 0)) {
 
-        {
-            long[] inc = Streams.longRange(10, 10, 1).toArray();
-            assertEquals(inc.length, 0);
-            long[] dec = Streams.longRange(10, 10, -1).toArray();
-            assertEquals(dec.length, 0);
-        }
+                        long d = end - start;
+                        long size = (d / step) + ((d % step == 0) ? 0 : 1);
+                        long[] exp = new long[(int) size];
+                        if (start < end) {
+                            for (long i = start, p = 0; i < end; i += step, p++) {
+                                exp[(int) p] = i;
+                            }
+                        } else {
+                            for (long i = start, p = 0; i > end; i += step, p++) {
+                                exp[(int) p] = i;
+                            }
+                        }
 
-        {
-            long[] inc = Streams.longRange(2, 10, 2).toArray();
-            long[] dec = Streams.longRange(8, 0, -2).toArray();
-            assertTrue(Arrays.equals(reverse(dec), inc));
-        }
+                        long[] inc = Streams.longRange(start, end, step).toArray();
+                        assertEquals(inc.length, size);
+                        assertTrue(Arrays.equals(exp, inc));
 
-        {
-            long[] inc = Streams.longRange(-10, -2, 2).toArray();
-            long[] dec = Streams.longRange(-4, -12, -2).toArray();
-            assertTrue(Arrays.equals(reverse(dec), inc));
-        }
+                        if (start != end) {
+                            long s = (start < end) ? 1 : -1;
+                            long[] dec = Streams.longRange(inc[inc.length - 1], inc[0] - s, -step).toArray();
+                            assertTrue(Arrays.equals(reverse(dec), inc));
+                        }
 
-        {
-            long[] inc = Streams.longRange(-10, 10, 2).toArray();
-            long[] dec = Streams.longRange(8, -12, -2).toArray();
-            assertTrue(Arrays.equals(reverse(dec), inc));
+                        withData(longRangeData(start, end, step)).stream(s -> s).
+                                expectedResult(exp).exercise();
+                    }
+                }
+            }
         }
     }
 
+    LongStreamTestData.LongStreamSupplierData longRangeData(long start, long end, long step) {
+        return new LongStreamTestData.LongStreamSupplierData(
+                "long range", () -> Streams.longRange(start, end, step));
+    }
+
     public void tesLongRangeReduce() {
-        long sum = Streams.longRange(0, 100000).reduce(0, Long::sum);
-        assertEquals(sum, Streams.longRange(0, 100000).parallel().reduce(0, Long::sum));
+        withData(longRangeData(0, 10000, 1)).
+                terminal(s -> s.reduce(0, Long::sum)).exercise();
     }
 
     public void testLongInfiniteRangeLimit() {
-        long sum = Streams.iterateLong(0, i -> i + 1).limit(10000).reduce(0, Long::sum);
-        assertEquals(sum, Streams.iterateLong(0, i -> i + 1).parallel().limit(10000).reduce(0, Long::sum));
+        withData(new LongStreamTestData.LongStreamSupplierData(
+                "long range", () -> Streams.iterateLong(0, i -> i + 1).limit(10000))).
+                terminal(s -> s.reduce(0, Long::sum)).exercise();
+    }
+
+    public void testLongInfiniteRangeFindFirst() {
+        long first = Streams.iterateLong(0, i -> i + 1).filter(i -> i > 10000).findFirst().getAsLong();
+        assertEquals(first, Streams.iterateLong(0, i -> i + 1).parallel().filter(i -> i > 10000).findFirst().getAsLong());
+    }
+
+    //
+
+    public void testDoubleRangeErrors() {
+        for (double start : Arrays.asList(1, 10, -1, -10)) {
+            for (double end : Arrays.asList(1, 10, -1, -10)) {
+                for (double step : Arrays.asList(0.0, +0.0, -0.0, 1.0, -1.0, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY)) {
+                    if ((start < end && step > 0) ||
+                        (start > end && step < 0) ||
+                        (start == end && step != 0)) {
+                        executeAndNoCatch(() -> Streams.doubleRange(start, end, step));
+                    }
+                    else {
+                        executeAndCatch(() -> Streams.doubleRange(start, end, step));
+                    }
+                }
+            }
+        }
+
+        for (double start : Arrays.asList(0.0, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN)) {
+            for (double end : Arrays.asList(0.0, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN)) {
+                for (double step : Arrays.asList(1.0, -1.0, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN)) {
+                    if (start == 0.0 && end == 0.0 && !Double.isNaN(step)) {
+                        executeAndNoCatch(() -> Streams.doubleRange(start, end, step));
+                    }
+                    else {
+                        executeAndCatch(() -> Streams.doubleRange(start, end, step));
+                    }
+                }
+            }
+        }
+    }
+
+    public void testDoubleRange() {
+        // Without step
+        for (double start : Arrays.asList(1, 1000, -1, -1000)) {
+            for (double end : Arrays.asList(1, 1000, -1, -1000)) {
+                double step = (start < end) ? 1 : -1;
+                double size = Math.ceil((end - start) / step);
+                double[] exp = new double[(int) size];
+                for (long i = 0; i < size; i++) {
+                    exp[(int) i] = start + i * step;
+                }
+
+                double[] inc = Streams.doubleRange(start, end).toArray();
+                assertEquals(inc.length, (int) size);
+                assertTrue(Arrays.equals(exp, inc));
+
+                if (start != end) {
+                    double[] dec = Streams.doubleRange(end - step, start - step).toArray();
+                    assertTrue(Arrays.equals(reverse(dec), inc));
+                }
+
+                withData(doubleRangeData(start, end, step)).stream(s -> s).
+                        expectedResult(exp).exercise();
+            }
+        }
+
+        // With step
+        for (double start : Arrays.asList(1, 1000, -1, -1000)) {
+            for (double end : Arrays.asList(1, 1000, -1, -1000)) {
+                for (double step : Arrays.asList(1, -1, -2, 2)) {
+                    if ((start < end && step > 0) ||
+                        (start > end && step < 0) ||
+                        (start == end && step != 0)) {
+
+                        double size = Math.ceil((end - start) / step);
+                        double[] exp = new double[(int) size];
+                        for (long i = 0; i < size; i++) {
+                            exp[(int) i] = start + i * step;
+                        }
+
+                        double[] inc = Streams.doubleRange(start, end, step).toArray();
+                        assertEquals(inc.length, (int) size);
+                        assertTrue(Arrays.equals(exp, inc));
+
+                        if (start != end) {
+                            double s = (start < end) ? 1 : -1;
+                            double[] dec = Streams.doubleRange(inc[inc.length - 1], inc[0] - s, -step).toArray();
+                            assertTrue(Arrays.equals(reverse(dec), inc));
+                        }
+
+                        withData(doubleRangeData(start, end, step)).stream(s -> s).
+                                expectedResult(exp).exercise();
+                    }
+                }
+            }
+        }
+
+        // With non-integer values
+        for (double step : Arrays.asList(Math.PI / 1000.0, Math.PI / 1000.0, Math.PI / 10000.0)) {
+            double start = -Math.PI;
+            double end = Math.PI;
+            double size = Math.ceil((end - start) / step);
+            double[] exp = new double[(int) size];
+            for (long i = 0; i < size; i++) {
+                exp[(int) i] = start + i * step;
+            }
+
+            withData(doubleRangeData(start, end, step)).stream(s -> s).
+                    expectedResult(exp).exercise();
+        }
+    }
+
+    DoubleStreamTestData.DoubleStreamSupplierData doubleRangeData(double start, double end, double step) {
+        return new DoubleStreamTestData.DoubleStreamSupplierData(
+                "double range", () -> Streams.doubleRange(start, end, step));
+    }
+
+    public void tesDoubleRangeReduce() {
+        withData(doubleRangeData(0, 10000, 1)).
+                terminal(s -> s.reduce(0, Double::sum)).exercise();
+    }
+
+    public void testDoubleInfiniteRangeLimit() {
+        withData(new DoubleStreamTestData.DoubleStreamSupplierData(
+                "double range", () -> Streams.iterateDouble(0, i -> i + 1).limit(10000))).
+                terminal(s -> s.reduce(0, Double::sum)).exercise();
+    }
+
+    public void testDoubleInfiniteRangeFindFirst() {
+        double first = Streams.iterateDouble(0, i -> i + 1).filter(i -> i > 10000).findFirst().getAsDouble();
+        assertEquals(first, Streams.iterateDouble(0, i -> i + 1).parallel().filter(i -> i > 10000).findFirst().getAsDouble());
     }
 
     //
@@ -238,10 +413,22 @@
         return b;
     }
 
+    private static double[] reverse(double[] a) {
+        double[] b = new double[a.length];
+        for (int i = 0; i < a.length; i++) {
+            b[b.length - i - 1] = a[i];
+        }
+        return b;
+    }
+
     private void executeAndCatch(Runnable r) {
         executeAndCatch(IllegalArgumentException.class, r);
     }
 
+    private void executeAndNoCatch(Runnable r) {
+        executeAndCatch(null, r);
+    }
+
     private void executeAndCatch(Class<? extends Exception> expected, Runnable r) {
         Exception caught = null;
         try {
@@ -250,12 +437,22 @@
         catch (Exception e) {
             caught = e;
         }
-        assertNotNull(caught,
-                      String.format("No Exception was thrown, expected an Exception of %s to be thrown",
-                                    expected.getName()));
-        assertTrue(expected.isInstance(caught),
-                   String.format("Exception thrown %s not an instance of %s",
-                                 caught.getClass().getName(), expected.getName()));
+
+        if (expected != null) {
+            assertNotNull(caught,
+                          String.format("No Exception was thrown, expected an Exception of %s to be thrown",
+                                        expected.getName()));
+            assertTrue(expected.isInstance(caught),
+                       String.format("Exception thrown %s not an instance of %s",
+                                     caught.getClass().getName(), expected.getName()));
+        }
+        else {
+            if (caught != null) {
+                assertNull(caught,
+                           String.format("Unexpected exception of %s was thrown",
+                                         caught.getClass().getName()));
+            }
+        }
     }
 
 }