Skip to content

Commit

Permalink
Implement & test javalib Random doubleStream methods (#3402)
Browse files Browse the repository at this point in the history
* Implement & test javalib Random doubleStream methods

* Tests work better when they are not commented out
  • Loading branch information
LeeTibbert committed Jul 20, 2023
1 parent 1a83676 commit 3544ba6
Show file tree
Hide file tree
Showing 3 changed files with 324 additions and 4 deletions.
99 changes: 99 additions & 0 deletions javalib/src/main/scala/java/util/Random.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package java.util

import java.{lang => jl}
import java.util.function.DoubleConsumer
import java.util.stream.{StreamSupport, DoubleStream}

import scala.annotation.tailrec

/** Ported from Apache Harmony and described by Donald E. Knuth in The Art of
Expand Down Expand Up @@ -110,4 +114,99 @@ class Random(seed_in: Long) extends AnyRef with java.io.Serializable {
// And return x*c
x * c
}

// The elements of the stream are random, not the Characteristics themselves.
final val randomStreamCharacteristics =
Spliterator.SIZED | Spliterator.SUBSIZED |
Spliterator.NONNULL | Spliterator.IMMUTABLE // 0x4540, decimal 17728

def doubles(): DoubleStream = {

val spliter =
new Spliterators.AbstractDoubleSpliterator(
jl.Long.MAX_VALUE,
randomStreamCharacteristics
) {
def tryAdvance(action: DoubleConsumer): Boolean = {
action.accept(nextDouble())
true
}
}

StreamSupport.doubleStream(spliter, parallel = false)
}

def doubles(
randomNumberOrigin: Double,
randomNumberBound: Double
): DoubleStream = {
doubles(jl.Long.MAX_VALUE, randomNumberOrigin, randomNumberBound)
}

private val invalidStreamSizeMsg = "size must be non-negative"

def doubles(streamSize: Long): DoubleStream = {
if (streamSize < 0L)
throw new IllegalArgumentException(invalidStreamSizeMsg)

val spliter =
new Spliterators.AbstractDoubleSpliterator(
streamSize,
randomStreamCharacteristics
) {
var nSeen = 0L

def tryAdvance(action: DoubleConsumer): Boolean = {
if (nSeen >= streamSize) false
else {
action.accept(nextDouble())
nSeen += 1
true
}
}
}

StreamSupport.doubleStream(spliter, parallel = false)

}

// Algorithm from JDK 17 Random Class documentation.
private def nextDouble(origin: Double, bound: Double): Double = {
val r = nextDouble() * (bound - origin) + origin

if (r >= bound) Math.nextDown(bound) // correct for rounding
else r
}

def doubles(
streamSize: Long,
randomNumberOrigin: Double,
randomNumberBound: Double
): DoubleStream = {
if (streamSize < 0L)
throw new IllegalArgumentException(invalidStreamSizeMsg)

if (!(randomNumberOrigin < randomNumberBound))
throw new IllegalArgumentException("bound must be greater than origin")

val spliter =
new Spliterators.AbstractDoubleSpliterator(
streamSize,
randomStreamCharacteristics
) {
var nSeen = 0L

def tryAdvance(action: DoubleConsumer): Boolean = {
if (nSeen >= streamSize) false
else {
action.accept(nextDouble(randomNumberOrigin, randomNumberBound))
nSeen += 1
true
}
}
}

StreamSupport.doubleStream(spliter, parallel = false)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ class ThreadLocalRandom private () extends Random {
)
}

def doubles(streamSize: Long): DoubleStream = {
override def doubles(streamSize: Long): DoubleStream = {
if (streamSize < 0L)
throw new IllegalArgumentException(ThreadLocalRandom.BAD_SIZE)
StreamSupport.doubleStream(
Expand All @@ -589,7 +589,7 @@ class ThreadLocalRandom private () extends Random {
)
}

def doubles(): DoubleStream =
override def doubles(): DoubleStream =
StreamSupport.doubleStream(
new ThreadLocalRandom.RandomDoublesSpliterator(
0L,
Expand All @@ -600,7 +600,7 @@ class ThreadLocalRandom private () extends Random {
false
)

def doubles(
override def doubles(
streamSize: Long,
randomNumberOrigin: Double,
randomNumberBound: Double
Expand All @@ -622,7 +622,7 @@ class ThreadLocalRandom private () extends Random {
)
}

def doubles(
override def doubles(
randomNumberOrigin: Double,
randomNumberBound: Double
): DoubleStream = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package org.scalanative.testsuite.javalib.util

import java.{lang => jl}

import java.util._
import java.util.function.DoubleConsumer

import org.junit.Test
import org.junit.Assert._
Expand All @@ -10,6 +13,8 @@ import scala.scalanative.junit.utils.AssumesHelper._

class RandomTest {

final val epsilon = 0.00000000 // tolerance for Floating point comparisons.

/** Helper class to access next */
class HackRandom(seed: Long) extends Random(seed) {
override def next(bits: Int): Int = super.next(bits)
Expand Down Expand Up @@ -242,4 +247,220 @@ class RandomTest {
assertTrue(random1.hashCode != random2.hashCode)
assertTrue(random1.nextInt != random2.nextInt)
}

final val expectedCharacteristics =
Spliterator.SIZED | Spliterator.IMMUTABLE |
Spliterator.NONNULL | Spliterator.SUBSIZED // 0x4540, decimal 17728

@Test def doublesZeroArg(): Unit = {
// doubles()

val seed = 0xa5a5a5a5a5a5a5a5L

val rng1 = new Random(seed)
val ds1 = rng1.doubles()

val ds1Spliter = ds1.spliterator()

assertEquals(
"characteristics",
expectedCharacteristics,
ds1Spliter.characteristics()
)

assertEquals("estimated size", jl.Long.MAX_VALUE, ds1Spliter.estimateSize())

assertEquals(
s"getExactSizeIfKnown",
jl.Long.MAX_VALUE,
ds1Spliter.getExactSizeIfKnown()
)

assertFalse(
"Expected sequential stream",
ds1.isParallel()
)

val rng2 = new Random(seed)
val ds2 = rng2.doubles()

val expectedContent = 0.10435154059121454

// for the skipTo element to be right, everything before it should be OK.
val actualContent = ds2
.skip(10)
.findFirst()
.orElse(0.0)

assertEquals("content", expectedContent, actualContent, epsilon)
}

@Test def doublesOneArg(): Unit = {
// doubles(long streamSize)
val seed = 0xa5a5a5a5a5a5a5a5L
val streamSize = 7

val rng1 = new Random(seed)
val ds1 = rng1.doubles(streamSize)

val ds1Spliter = ds1.spliterator()

assertEquals(
"characteristics",
expectedCharacteristics,
ds1Spliter.characteristics()
)

assertEquals("estimated size", streamSize, ds1Spliter.estimateSize())

assertEquals(
s"getExactSizeIfKnown",
streamSize,
ds1Spliter.getExactSizeIfKnown()
)

assertFalse(
"Expected sequential stream",
ds1.isParallel()
)

val rng2 = new Random(seed)
val ds2 = rng2.doubles(streamSize)

assertEquals("count", streamSize, ds2.count())

val rng3 = new Random(seed)
val ds3 = rng3.doubles(streamSize)

val expectedContent = 0.6915186905201246

// for the skipTo element to be right, everything before it should be OK.
val actualContent = ds3
.skip(5)
.findFirst()
.orElse(0.0)

assertEquals("content", expectedContent, actualContent, epsilon)
}

@Test def doublesTwoArg(): Unit = {
// doubles(double randomNumberOrigin, double randomNumberBound)

// This test is not guaranteed. It samples to build correctness confidence.

val seed = 0xa5a5a5a5a5a5a5a5L
val streamSize = 100

val rnOrigin = 2.0
val rnBound = 80.0

val rng1 = new Random(seed)
val ds1 = rng1.doubles(rnOrigin, rnBound)

val ds1Spliter = ds1.spliterator()

assertEquals(
"characteristics",
expectedCharacteristics,
ds1Spliter.characteristics()
)

assertEquals("estimated size", jl.Long.MAX_VALUE, ds1Spliter.estimateSize())

assertEquals(
s"getExactSizeIfKnown",
jl.Long.MAX_VALUE,
ds1Spliter.getExactSizeIfKnown()
)

assertFalse(
"Expected sequential stream",
ds1.isParallel()
)

val rng2 = new Random(seed)
val ds2 = rng2
.doubles(rnOrigin, rnBound)
.limit(streamSize)

// Keep Scala 2 happy. Can use lambda when only Scala > 2 is supported.
val doubleConsumer = new DoubleConsumer {
def accept(d: Double): Unit = {
assertTrue(
s"Found value ${d} < low bound ${rnOrigin}",
d >= rnOrigin
)

assertTrue(
s"Found value ${d} >= high bound ${rnBound}",
d < rnBound
)
}
}

ds2.forEach(doubleConsumer)
}

@Test def doublesThreeArg(): Unit = {
// doubles(long streamSize, double randomNumberOrigin,
// double randomNumberBound)

// This test is not guaranteed. It samples to build correctness confidence.

val seed = 0xa5a5a5a5a5a5a5a5L
val streamSize = 100

val rnOrigin = 1.0
val rnBound = 90.0

val rng1 = new Random(seed)
val ds1 = rng1.doubles(streamSize, rnOrigin, rnBound)

val ds1Spliter = ds1.spliterator()

assertEquals(
"characteristics",
expectedCharacteristics,
ds1Spliter.characteristics()
)

assertEquals("estimated size", streamSize, ds1Spliter.estimateSize())

assertEquals(
s"getExactSizeIfKnown",
streamSize,
ds1Spliter.getExactSizeIfKnown()
)

assertFalse(
"Expected sequential stream",
ds1.isParallel()
)

val rng2 = new Random(seed)
val ds2 = rng2.doubles(streamSize, rnOrigin, rnBound)

assertEquals("count", streamSize, ds2.count())

val rng3 = new Random(seed)
val ds3 = rng3.doubles(streamSize, rnOrigin, rnBound)

// Keep Scala 2 happy. Can use lambda when only Scala > 2 is supported.
val doubleConsumer = new DoubleConsumer {
def accept(d: Double): Unit = {
assertTrue(
s"Found value ${d} < low bound ${rnOrigin}",
d >= rnOrigin
)

assertTrue(
s"Found value ${d} >= high bound ${rnBound}",
d < rnBound
)
}
}

ds3.forEach(doubleConsumer)
}

}

0 comments on commit 3544ba6

Please sign in to comment.