8000 Fix #5097: Implement most of `ju.random.RandomGenerator`. · scala-js/scala-js@9cb865f · GitHub
[go: up one dir, main page]

Skip to content

Commit 9cb865f

Browse files
committed
Fix #5097: Implement most of ju.random.RandomGenerator.
It is bit-for-bit compatible with what the JDK produces. We did not implement the sub-interfaces of `RandomGenerator`. We also did not implement the methods `nextGaussian` and `nextExponential`.
1 parent fec4ae7 commit 9cb865f

File tree

6 files changed

+790
-122
lines changed

6 files changed

+790
-122
lines changed

javalib/src/main/scala/java/util/Random.scala

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ import scala.annotation.tailrec
1717
import scala.scalajs.js
1818
import scala.scalajs.LinkingInfo
1919

20-
class Random(seed_in: Long) extends AnyRef with java.io.Serializable {
20+
import java.util.random.RandomGenerator
21+
22+
class Random(seed_in: Long)
23+
extends AnyRef with RandomGenerator with java.io.Serializable {
24+
2125
/* This class has two different implementations of seeding and computing
2226
* bits, depending on whether we are on Wasm or JS. On Wasm, we use the
2327
* implementation specified in the JavaDoc verbatim. On JS, however, that is
@@ -108,16 +112,16 @@ class Random(seed_in: Long) extends AnyRef with java.io.Serializable {
108112
result32 >>> (32 - bits)
109113
}
110114

111-
def nextDouble(): Double = {
115+
override def nextDouble(): Double = {
112116
// ((next(26).toLong << 27) + next(27)) / (1L << 53).toDouble
113117
((next(26).toDouble * (1L << 27).toDouble) + next(27).toDouble) / (1L << 53).toDouble
114118
}
115119

116-
def nextBoolean(): Boolean = next(1) != 0
120+
override def nextBoolean(): Boolean = next(1) != 0
117121

118-
def nextInt(): Int = next(32)
122+
override def nextInt(): Int = next(32)
119123

120-
def nextInt(n: Int): Int = {
124+
override def nextInt(n: Int): Int = {
121125
if (n <= 0) {
122126
throw new IllegalArgumentException("n must be positive")
123127
} else if ((n & -n) == n) { // i.e., n is a power of 2
@@ -148,12 +152,12 @@ class Random(seed_in: Long) extends AnyRef with java.io.Serializable {
148152

149153
def nextLong(): Long = (next(32).toLong << 32) + next(32)
150154

151-
def nextFloat(): Float = {
155+
override def nextFloat(): Float = {
152156
// next(24).toFloat / (1 << 24).toFloat
153157
(next(24).toDouble / (1 << 24).toDouble).toFloat
154158
}
155159

156-
def nextBytes(bytes: Array[Byte]): Unit = {
160+
override def nextBytes(bytes: Array[Byte]): Unit = {
157161
var i = 0
158162
while (i < bytes.length) {
159163
var rnd = nextInt()

javalib/src/main/scala/java/util/SplittableRandom.scala

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
package java.util
1414

15+
import java.util.random.RandomGenerator
16+
1517
/*
1618
* This is a clean room implementation derived from the original paper
1719
* and Java implementation mentioned there:
@@ -23,7 +25,6 @@ package java.util
2325
*/
2426
private object SplittableRandom {
2527

26-
private final val DoubleULP = 1.0 / (1L << 53)
2728
private final val GoldenGamma = 0x9e3779b97f4a7c15L
2829

2930
private var defaultGen: Long = new Random().nextLong()
@@ -80,7 +81,8 @@ private object SplittableRandom {
8081

8182
}
8283

83-
final class SplittableRandom private (private var seed: Long, gamma: Long) {
84+
final class SplittableRandom private (private var seed: Long, gamma: Long)
85+
extends RandomGenerator {
8486
import SplittableRandom._
8587

8688
def this(seed: Long) = {
@@ -106,27 +108,13 @@ final class SplittableRandom private (private var seed: Long, gamma: Long) {
106108
seed
107109
}
108110

109-
def nextInt(): Int = mix32(nextSeed())
110-
111-
//def nextInt(bound: Int): Int
112-
113-
//def nextInt(origin: Int, bound: Int): Int
111+
/* According to the JavaDoc, this method is not overridden anymore.
112+
* However, if we remove our override, we break tests in
113+
* `SplittableRandomTest`. I don't know how the JDK produces the values it
114+
* produces without that override. So we keep it on our side.
115+
*/
116+
override def nextInt(): Int = mix32(nextSeed())
114117

115118
def nextLong(): Long = mix64(nextSeed())
116119

117-
//def nextLong(bound: Long): Long
118-
119-
//def nextLong(origin: Long, bound: Long): Long
120-
121-
def nextDouble(): Double =
122-
(nextLong() >>> 11).toDouble * DoubleULP
123-
124-
//def nextDouble(bound: Double): Double
125-
126-
//def nextDouble(origin: Double, bound: Double): Double
127-
128-
// this should be properly tested
129-
// looks to work but just by chance maybe
130-
def nextBoolean(): Boolean = nextInt() < 0
131-
132120
}

javalib/src/main/scala/java/util/concurrent/ThreadLocalRandom.scala

Lines changed: 0 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
package java.util.concurrent
1010

1111
import java.util.Random
12-
import scala.annotation.tailrec
1312

1413
class ThreadLocalRandom extends Random {
1514

@@ -22,98 +21,6 @@ class ThreadLocalRandom extends Random {
2221

2322
super.setSeed(seed)
2423
}
25-
26-
def nextInt(least: Int, bound: Int): Int = {
27-
if (least >= bound)
28-
throw new IllegalArgumentException()
29-
30-
val difference = bound - least
31-
if (difference > 0) {
32-
nextInt(difference) + least
33-
} else {
34-
/* The interval size here is greater than Int.MaxValue,
35-
* so the loop will exit with a probability of at least 1/2.
36-
*/
37-
@tailrec
38-
def loop(): Int = {
39-
val n = nextInt()
40-
if (n >= least && n < bound) n
41-
else loop()
42-
}
43-
44-
loop()
45-
}
46-
}
47-
48-
def nextLong(_n: Long): Long = {
49-
if (_n <= 0)
50-
throw new IllegalArgumentException("n must be positive")
51-
52-
/*
53-
* Divide n by two until small enough for nextInt. On each
54-
* iteration (at most 31 of them but usually much less),
55-
* randomly choose both whether to include high bit in result
56-
* (offset) and whether to continue with the lower vs upper
57-
* half (which makes a difference only if odd).
58-
*/
59-
60-
var offset = 0L
61-
var n = _n
62-
63-
while (n >= Integer.MAX_VALUE) {
64-
val bits = next(2)
65-
val halfn = n >>> 1
66-
val nextn =
67-
if ((bits & 2) == 0) halfn
68-
else n - halfn
69-
if ((bits & 1) == 0)
70-
offset += n - nextn
71-
n = nextn
72-
}
73-
offset + nextInt(n.toInt)
74-
}
75-
76-
def nextLong(least: Long, bound: Long): Long = {
77-
if (least >= bound)
78-
throw new IllegalArgumentException()
79-
80-
val difference = bound - least
81-
if (difference > 0) {
82-
nextLong(difference) + least
83-
} else {
84-
/* The interval size here is greater than Long.MaxValue,
85-
* so the loop will exit with a probability of at least 1/2.
86-
*/
87-
@tailrec
88-
def loop(): Long = {
89-
val n = nextLong()
90-
if (n >= least && n < bound) n
91-
else loop()
92-
}
93-
94-
loop()
95-
}
96-
}
97-
98-
def nextDouble(n: Double): Double = {
99-
if (n <= 0)
100-
throw new IllegalArgumentException("n must be positive")
101-
102-
nextDouble() * n
103-
}
104-
105-
def nextDouble(least: Double, bound: Double): Double = {
106-
if (least >= bound)
107-
throw new IllegalArgumentException()
108-
109-
/* Based on documentation for Random.doubles to avoid issue #2144 and other
110-
* possible rounding up issues:
111-
* https://docs.oracle.com/javase/8/docs/api/java/util/Random.html#doubles-double-double-
112-
*/
113-
val next = nextDouble() * (bound - least) + least
114-
if (next < bound) next
115-
else Math.nextAfter(bound, Double.NegativeInfinity)
116-
}
11724
}
11825

11926
object ThreadLocalRandom {

0 commit comments

Comments
 (0)
0