Provide common Random API

#KT-17261
This commit is contained in:
Ilya Gorbunov
2018-07-03 03:43:34 +03:00
parent 9df411481c
commit cc031e3a40
9 changed files with 415 additions and 0 deletions
@@ -18,9 +18,13 @@
package kotlin.internal.jdk7
import kotlin.internal.PlatformImplementations
import kotlin.random.Random
import kotlin.random.jdk7.PlatformThreadLocalRandom
internal open class JDK7PlatformImplementations : PlatformImplementations() {
override fun addSuppressed(cause: Throwable, exception: Throwable) = cause.addSuppressed(exception)
override fun defaultPlatformRandom(): Random = PlatformThreadLocalRandom()
}
@@ -0,0 +1,18 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package kotlin.random.jdk7
import java.util.concurrent.ThreadLocalRandom
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "CANNOT_OVERRIDE_INVISIBLE_MEMBER")
internal class PlatformThreadLocalRandom : kotlin.random.AbstractPlatformRandom() {
override val impl: ThreadLocalRandom get() = ThreadLocalRandom.current()
override fun nextLong(bound: Long): Long = impl.nextLong(bound)
override fun nextLong(origin: Long, bound: Long): Long = impl.nextLong(origin, bound)
override fun nextInt(bound: Int): Int = impl.nextInt(bound)
override fun nextInt(origin: Int, bound: Int): Int = impl.nextInt(origin, bound)
}
@@ -0,0 +1,24 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package kotlin.random
import kotlin.math.pow
internal actual fun defaultPlatformRandom(): Random =
XorWowRandom((js("Math").random().unsafeCast<Double>() * (2.0.pow(32.0))).toInt())
internal actual fun fastLog2(value: Int): Int {
// TODO: not so fast, make faster
require(value > 0)
var v = value
var log = -1
while (v != 0) {
v = v.ushr(1)
log++
}
return log
}
@@ -7,6 +7,8 @@ package kotlin.internal
import kotlin.*
import java.util.regex.MatchResult
import kotlin.random.FallbackThreadLocalRandom
import kotlin.random.Random
internal open class PlatformImplementations {
@@ -17,6 +19,8 @@ internal open class PlatformImplementations {
public open fun getMatchResultNamedGroup(matchResult: MatchResult, name: String): MatchGroup? {
throw UnsupportedOperationException("Retrieving groups by name is not supported on this platform.")
}
public open fun defaultPlatformRandom(): Random = FallbackThreadLocalRandom()
}
@@ -0,0 +1,73 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package kotlin.random
import kotlin.UnsupportedOperationException
import kotlin.internal.*
@SinceKotlin("1.3")
public fun Random.asJavaRandom(): java.util.Random =
(this as? AbstractPlatformRandom)?.impl ?: KotlinRandom(this)
@SinceKotlin("1.3")
public fun java.util.Random.asKotlinRandom(): Random =
PlatformRandom(this)
@InlineOnly
internal actual inline fun defaultPlatformRandom(): Random =
IMPLEMENTATIONS.defaultPlatformRandom()
internal actual fun fastLog2(value: Int): Int =
31 - Integer.numberOfLeadingZeros(value)
internal abstract class AbstractPlatformRandom : Random() {
abstract val impl: java.util.Random
override fun nextBits(bitCount: Int): Int = impl.nextInt() ushr (32 - bitCount).coerceAtLeast(0)
override fun nextInt(): Int = impl.nextInt()
override fun nextLong(): Long = impl.nextLong()
override fun nextBoolean(): Boolean = impl.nextBoolean()
override fun nextDouble(): Double = impl.nextDouble()
override fun nextFloat(): Float = impl.nextFloat()
override fun nextBytes(array: ByteArray): ByteArray = run { impl.nextBytes(array); array }
}
internal class FallbackThreadLocalRandom : AbstractPlatformRandom() {
private val implStorage = object : ThreadLocal<java.util.Random>() {
override fun initialValue(): java.util.Random {
return java.util.Random()
}
}
override val impl: java.util.Random
get() = implStorage.get()
}
private class PlatformRandom(override val impl: java.util.Random) : AbstractPlatformRandom()
private class KotlinRandom(val impl: Random) : java.util.Random() {
override fun next(bits: Int): Int = impl.nextBits(bits)
override fun nextInt(): Int = impl.nextInt()
override fun nextInt(bound: Int): Int = impl.nextInt(bound)
override fun nextBoolean(): Boolean = impl.nextBoolean()
override fun nextLong(): Long = impl.nextLong()
override fun nextFloat(): Float = impl.nextFloat()
override fun nextDouble(): Double = impl.nextDouble()
override fun setSeed(seed: Long) {
throw UnsupportedOperationException("Setting seed is not supported")
}
override fun nextBytes(bytes: ByteArray) {
impl.nextBytes(bytes)
}
}
@@ -0,0 +1,41 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package samples.random
import samples.*
import kotlin.random.Random
import kotlin.test.assertTrue
class Randoms {
@Sample
fun defaultRandom() {
val randomValues = List(10) { Random.nextInt(0, 100) }
// prints new sequence every time
println(randomValues)
val nextValues = List(10) { Random.nextInt(0, 100) }
println(nextValues)
assertTrue(randomValues != nextValues)
}
@Sample
fun seededRandom() {
fun getRandomList(random: Random): List<Int> =
List(10) { random.nextInt(0, 100) }
val randomValues1 = getRandomList(Random(42))
// prints the same sequence every time
assertPrints(randomValues1, "[44, 34, 69, 67, 22, 16, 67, 45, 95, 10]")
val randomValues2 = getRandomList(Random(42))
// random with the same seed produce the same sequence
assertTrue(randomValues1 == randomValues2)
val randomValues3 = getRandomList(Random(0))
// random with another seed produce another sequence
assertPrints(randomValues3, "[20, 63, 41, 28, 0, 99, 35, 42, 72, 13]")
}
}
@@ -0,0 +1,176 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package kotlin.random
import kotlin.*
/**
* An abstract class that is implemented by random number generator algorithms.
*
* The companion object [Random.Companion] is the default instance of [Random].
*
* To get a seeded instance of random generator use [Random] function.
*
* @sample samples.random.Randoms.defaultRandom
*/
@SinceKotlin("1.3")
public abstract class Random {
public abstract fun nextBits(bitCount: Int): Int
public open fun nextInt(): Int = nextBits(32)
public open fun nextInt(bound: Int): Int = nextInt(0, bound)
public open fun nextInt(origin: Int, bound: Int): Int {
checkRangeBounds(origin.toLong(), bound.toLong())
val n = bound - origin
if (n > 0 || n == Int.MIN_VALUE) {
val rnd = if (n and -n == n) {
val bitCount = fastLog2(n)
nextBits(bitCount)
} else {
var v: Int
do {
val bits = nextInt().ushr(1)
v = bits % n
} while (bits - v + (n - 1) < 0)
v
}
return origin + rnd
} else {
while (true) {
val rnd = nextInt()
if (rnd in origin until bound) return rnd
}
}
}
public open fun nextInt(range: IntRange): Int = when {
range.last < Int.MAX_VALUE -> nextInt(range.first, range.last + 1)
range.first > Int.MIN_VALUE -> nextInt(range.first - 1, range.last) + 1
else -> nextInt()
}
public open fun nextLong(): Long = nextInt().toLong().shl(32) + nextInt()
public open fun nextLong(bound: Long): Long = nextLong(0, bound)
public open fun nextLong(origin: Long, bound: Long): Long {
checkRangeBounds(origin, bound)
val n = bound - origin
if (n > 0) {
val rnd: Long
if (n and -n == n) {
val bitCount = fastLog2((n ushr 32).toInt())
rnd = when {
bitCount < 0 ->
// toUInt().toLong()
nextBits(fastLog2(n.toInt())).toLong() and 0xFFFFFFFF
bitCount == 0 ->
nextBits(32).toLong() and 0xFFFFFFFF
else ->
nextBits(bitCount).toLong().shl(32) + nextBits(32)
}
} else {
var v: Long
do {
val bits = nextLong().ushr(1)
v = bits % n
} while (bits - v + (n - 1) < 0)
rnd = v
}
return origin + rnd
} else {
while (true) {
val rnd = nextLong()
if (rnd in origin until bound) return rnd
}
}
}
public open fun nextLong(range: LongRange): Long = when {
// range.isEmpty() -> throw IllegalArgumentException("Cannot get random in empty range: $range")
range.last < Long.MAX_VALUE -> nextLong(range.start, range.endInclusive + 1)
range.start > Long.MIN_VALUE -> nextLong(range.start - 1, range.endInclusive) + 1
else -> nextLong()
}
public open fun nextBoolean(): Boolean = nextBits(1) != 0
public open fun nextDouble(): Double = (nextBits(26).toLong().shl(27) + nextBits(27)) / (1L shl 53).toDouble()
public open fun nextFloat(): Float = nextBits(24) / (1 shl 24).toFloat()
public open fun nextBytes(array: ByteArray, startIndex: Int = 0, endIndex: Int = array.size): ByteArray {
// TODO: check range
var v: Int = 0
var bits: Int = 0
for (i in startIndex until endIndex) {
if (bits == 0) {
bits = if (endIndex - i >= 4) 32 else (endIndex - i) * 8
v = nextBits(bits)
}
array[i] = v.toByte()
v = v ushr 8
bits -= 8
}
return array
}
public open fun nextBytes(array: ByteArray): ByteArray = nextBytes(array, 0, array.size)
public open fun nextBytes(size: Int): ByteArray = nextBytes(ByteArray(size))
// TODO: UInt, ULong, UByteArray
/**
* The default random number generator.
*
*
*
* @sample samples.random.Randoms.defaultRandom
*/
companion object : Random() {
private var _defaultRandom: Random? = null
private val defaultRandom: Random
get() = _defaultRandom ?: defaultPlatformRandom().also { _defaultRandom = it }
override fun nextBits(bitCount: Int): Int = defaultRandom.nextBits(bitCount)
// TODO: Override others
internal fun checkRangeBounds(origin: Long, bound: Long) {
if (origin >= bound) throw IllegalArgumentException("Random range is empty: [$origin, $bound)")
}
}
}
/**
* Returns a repeatable random number generator seeded with the given [seed] `Int` value.
*
* Two generators with the same seed produce the same sequence of values.
*
* @sample samples.random.Randoms.seededRandom
*/
@SinceKotlin("1.3")
public fun Random(seed: Int): Random = XorWowRandom(seed)
/**
* Returns a repeatable random number generator seeded with the given [seed] `Long` value.
*
* Two generators with the same seed produce the same sequence of values.
*
* @sample samples.random.Randoms.seededRandom
*/
@SinceKotlin("1.3")
public fun Random(seed: Long): Random = XorWowRandom(seed)
internal expect fun defaultPlatformRandom(): Random
internal expect fun fastLog2(value: Int): Int // 31 - Integer.numberOfLeadingZeros(value)
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package kotlin.random
internal class XorWowRandom internal constructor(
private var s0: Int,
private var s1: Int,
private var s2: Int,
private var s3: Int,
private var addent: Int
) : Random() {
public constructor(seed: Long) : this(seed.toInt(), (seed ushr 32).toInt(), 0, if (seed == 0L) 1 else 0, 1)
public constructor(seed: Int) : this(seed, 0, 0, if (seed == 0) 1 else 0, 1)
init {
require((s0 or s1 or s2 or s3) != 0) { "Initial state must have at least one non-zero element" }
}
/* Algorithm "xorwow" from p. 5 of Marsaglia, "Xorshift RNGs" */
override fun nextInt(): Int {
var t = s3
t = t xor (t shr 2)
t = t xor (t shl 1)
s3 = s2
s2 = s1
val s = s0
s1 = s
t = t xor s
t = t xor (s shl 4)
s0 = t
addent += 362437
return t + addent
}
override fun nextBits(bitCount: Int): Int = nextInt() ushr (32 - bitCount)
}
@@ -3864,6 +3864,41 @@ public abstract interface class kotlin/properties/ReadWriteProperty {
public abstract fun setValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;Ljava/lang/Object;)V
}
public final class kotlin/random/PlatformRandomKt {
public static final fun asJavaRandom (Lkotlin/random/Random;)Ljava/util/Random;
public static final fun asKotlinRandom (Ljava/util/Random;)Lkotlin/random/Random;
}
public abstract class kotlin/random/Random {
public static final field Companion Lkotlin/random/Random$Companion;
public fun <init> ()V
public abstract fun nextBits (I)I
public fun nextBoolean ()Z
public fun nextBytes (I)[B
public fun nextBytes ([B)[B
public fun nextBytes ([BII)[B
public static synthetic fun nextBytes$default (Lkotlin/random/Random;[BIIILjava/lang/Object;)[B
public fun nextDouble ()D
public fun nextFloat ()F
public fun nextInt ()I
public fun nextInt (I)I
public fun nextInt (II)I
public fun nextInt (Lkotlin/ranges/IntRange;)I
public fun nextLong ()J
public fun nextLong (J)J
public fun nextLong (JJ)J
public fun nextLong (Lkotlin/ranges/LongRange;)J
}
public final class kotlin/random/Random$Companion : kotlin/random/Random {
public fun nextBits (I)I
}
public final class kotlin/random/RandomKt {
public static final fun Random (I)Lkotlin/random/Random;
public static final fun Random (J)Lkotlin/random/Random;
}
public class kotlin/ranges/CharProgression : java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker {
public static final field Companion Lkotlin/ranges/CharProgression$Companion;
public fun equals (Ljava/lang/Object;)Z