Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement & test javalib Random doubleStream methods #3402

Merged
merged 2 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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)
}

}