Random docs and API refinement

KT-17261
This commit is contained in:
Ilya Gorbunov
2018-07-04 04:50:45 +03:00
parent 7e4528e217
commit fa33b1b5a9
6 changed files with 203 additions and 31 deletions
@@ -11,8 +11,9 @@ import java.util.concurrent.ThreadLocalRandom
internal class PlatformThreadLocalRandom : kotlin.random.AbstractPlatformRandom() { internal class PlatformThreadLocalRandom : kotlin.random.AbstractPlatformRandom() {
override val impl: ThreadLocalRandom get() = ThreadLocalRandom.current() override val impl: ThreadLocalRandom get() = ThreadLocalRandom.current()
override fun nextInt(origin: Int, bound: Int): Int = impl.nextInt(origin, bound)
override fun nextLong(bound: Long): Long = impl.nextLong(bound) override fun nextLong(bound: Long): Long = impl.nextLong(bound)
override fun nextLong(origin: Long, bound: Long): Long = impl.nextLong(origin, bound) override fun nextLong(origin: Long, bound: Long): Long = impl.nextLong(origin, bound)
override fun nextInt(bound: Int): Int = impl.nextInt(bound) override fun nextDouble(bound: Double): Double = impl.nextDouble(bound)
override fun nextInt(origin: Int, bound: Int): Int = impl.nextInt(origin, bound) override fun nextDouble(origin: Double, bound: Double): Double = impl.nextDouble(origin, bound)
} }
@@ -13,7 +13,6 @@ internal actual fun defaultPlatformRandom(): Random =
internal actual fun fastLog2(value: Int): Int { internal actual fun fastLog2(value: Int): Int {
// TODO: not so fast, make faster // TODO: not so fast, make faster
require(value > 0)
var v = value var v = value
var log = -1 var log = -1
while (v != 0) { while (v != 0) {
@@ -8,13 +8,19 @@ package kotlin.random
import kotlin.UnsupportedOperationException import kotlin.UnsupportedOperationException
import kotlin.internal.* import kotlin.internal.*
/**
* Creates a [java.util.Random][java.util.Random] instance that uses the specified Kotlin [Random] generator as a randomness source.
*/
@SinceKotlin("1.3") @SinceKotlin("1.3")
public fun Random.asJavaRandom(): java.util.Random = public fun Random.asJavaRandom(): java.util.Random =
(this as? AbstractPlatformRandom)?.impl ?: KotlinRandom(this) (this as? AbstractPlatformRandom)?.impl ?: KotlinRandom(this)
/**
* Creates a Kotlin [Random] instance that uses the specified [java.util.Random][java.util.Random] generator as a randomness source.
*/
@SinceKotlin("1.3") @SinceKotlin("1.3")
public fun java.util.Random.asKotlinRandom(): Random = public fun java.util.Random.asKotlinRandom(): Random =
PlatformRandom(this) (this as? KotlinRandom)?.impl ?: PlatformRandom(this)
@@ -32,12 +38,12 @@ internal abstract class AbstractPlatformRandom : Random() {
override fun nextBits(bitCount: Int): Int = impl.nextInt() ushr (32 - bitCount).coerceAtLeast(0) override fun nextBits(bitCount: Int): Int = impl.nextInt() ushr (32 - bitCount).coerceAtLeast(0)
override fun nextInt(): Int = impl.nextInt() override fun nextInt(): Int = impl.nextInt()
override fun nextInt(bound: Int): Int = impl.nextInt(bound)
override fun nextLong(): Long = impl.nextLong() override fun nextLong(): Long = impl.nextLong()
override fun nextBoolean(): Boolean = impl.nextBoolean() override fun nextBoolean(): Boolean = impl.nextBoolean()
override fun nextDouble(): Double = impl.nextDouble() override fun nextDouble(): Double = impl.nextDouble()
override fun nextFloat(): Float = impl.nextFloat() override fun nextFloat(): Float = impl.nextFloat()
override fun nextBytes(array: ByteArray): ByteArray = array.also { impl.nextBytes(it) }
override fun nextBytes(array: ByteArray): ByteArray = run { impl.nextBytes(array); array }
} }
internal class FallbackThreadLocalRandom : AbstractPlatformRandom() { internal class FallbackThreadLocalRandom : AbstractPlatformRandom() {
@@ -61,13 +67,13 @@ private class KotlinRandom(val impl: Random) : java.util.Random() {
override fun nextFloat(): Float = impl.nextFloat() override fun nextFloat(): Float = impl.nextFloat()
override fun nextDouble(): Double = impl.nextDouble() override fun nextDouble(): Double = impl.nextDouble()
override fun setSeed(seed: Long) {
throw UnsupportedOperationException("Setting seed is not supported")
}
override fun nextBytes(bytes: ByteArray) { override fun nextBytes(bytes: ByteArray) {
impl.nextBytes(bytes) impl.nextBytes(bytes)
} }
override fun setSeed(seed: Long) {
throw UnsupportedOperationException("Setting seed is not supported.")
}
} }
+166 -20
View File
@@ -6,6 +6,7 @@
package kotlin.random package kotlin.random
import kotlin.* import kotlin.*
import kotlin.math.nextDown
/** /**
* An abstract class that is implemented by random number generator algorithms. * An abstract class that is implemented by random number generator algorithms.
@@ -18,12 +19,43 @@ import kotlin.*
*/ */
@SinceKotlin("1.3") @SinceKotlin("1.3")
public abstract class Random { public abstract class Random {
/**
* Gets the next random [bitCount] number of bits.
*
* Generates an `Int` whose lower [bitCount] bits are filled with random values and the remaining upper bits are zero.
*
* @param bitCount number of bits to generate, must be in range 0..32, otherwise the behavior is unspecified.
*/
public abstract fun nextBits(bitCount: Int): Int public abstract fun nextBits(bitCount: Int): Int
/**
* Gets the next random `Int` from the random number generator.
*
* Generates an `Int` random value uniformly distributed between `Int.MIN_VALUE` and `Int.MAX_VALUE` (inclusive).
*/
public open fun nextInt(): Int = nextBits(32) public open fun nextInt(): Int = nextBits(32)
/**
* Gets the next random non-negative `Int` from the random number generator not greater than the specified [bound].
*
* Generates an `Int` random value uniformly distributed between `0` (inclusive) and the specified [bound] (exclusive).
*
* @param bound must be positive.
*
* @throws IllegalArgumentException if [bound] is negative or zero.
*/
public open fun nextInt(bound: Int): Int = nextInt(0, bound) public open fun nextInt(bound: Int): Int = nextInt(0, bound)
/**
* Gets the next random `Int` from the random number generator in the specified range.
*
* Generates an `Int` random value uniformly distributed between the specified [origin] (inclusive) and the specified [bound] (exclusive).
*
* @throws IllegalArgumentException if [origin] is greater than or equal to [bound].
*/
public open fun nextInt(origin: Int, bound: Int): Int { public open fun nextInt(origin: Int, bound: Int): Int {
checkRangeBounds(origin.toLong(), bound.toLong()) checkRangeBounds(origin, bound)
val n = bound - origin val n = bound - origin
if (n > 0 || n == Int.MIN_VALUE) { if (n > 0 || n == Int.MIN_VALUE) {
val rnd = if (n and -n == n) { val rnd = if (n and -n == n) {
@@ -46,17 +78,45 @@ public abstract class Random {
} }
} }
/**
* Gets the next random `Int` from the random number generator in the specified [range].
*
* Generates an `Int` random value uniformly distributed in the specified [range]:
* from `range.start` inclusive to `range.endInclusive` inclusive.
*
* @throws IllegalArgumentException if [range] is empty.
*/
public open fun nextInt(range: IntRange): Int = when { public open fun nextInt(range: IntRange): Int = when {
range.last < Int.MAX_VALUE -> nextInt(range.first, range.last + 1) range.last < Int.MAX_VALUE -> nextInt(range.first, range.last + 1)
range.first > Int.MIN_VALUE -> nextInt(range.first - 1, range.last) + 1 range.first > Int.MIN_VALUE -> nextInt(range.first - 1, range.last) + 1
else -> nextInt() else -> nextInt()
} }
/**
* Gets the next random `Long` from the random number generator.
*
* Generates a `Long` random value uniformly distributed between `Long.MIN_VALUE` and `Long.MAX_VALUE` (inclusive).
*/
public open fun nextLong(): Long = nextInt().toLong().shl(32) + nextInt() public open fun nextLong(): Long = nextInt().toLong().shl(32) + nextInt()
/**
* Gets the next random non-negative `Long` from the random number generator not greater than the specified [bound].
*
* Generates a `Long` random value uniformly distributed between `0` (inclusive) and the specified [bound] (exclusive).
*
* @param bound must be positive.
*
* @throws IllegalArgumentException if [bound] is negative or zero.
*/
public open fun nextLong(bound: Long): Long = nextLong(0, bound) public open fun nextLong(bound: Long): Long = nextLong(0, bound)
/**
* Gets the next random `Long` from the random number generator in the specified range.
*
* Generates a `Long` random value uniformly distributed between the specified [origin] (inclusive) and the specified [bound] (exclusive).
*
* @throws IllegalArgumentException if [origin] is greater than or equal to [bound].
*/
public open fun nextLong(origin: Long, bound: Long): Long { public open fun nextLong(origin: Long, bound: Long): Long {
checkRangeBounds(origin, bound) checkRangeBounds(origin, bound)
val n = bound - origin val n = bound - origin
@@ -90,6 +150,14 @@ public abstract class Random {
} }
} }
/**
* Gets the next random `Long` from the random number generator in the specified [range].
*
* Generates a `Long` random value uniformly distributed in the specified [range]:
* from `range.start` inclusive to `range.endInclusive` inclusive.
*
* @throws IllegalArgumentException if [range] is empty.
*/
public open fun nextLong(range: LongRange): Long = when { public open fun nextLong(range: LongRange): Long = when {
// range.isEmpty() -> throw IllegalArgumentException("Cannot get random in empty range: $range") // range.isEmpty() -> throw IllegalArgumentException("Cannot get random in empty range: $range")
range.last < Long.MAX_VALUE -> nextLong(range.start, range.endInclusive + 1) range.last < Long.MAX_VALUE -> nextLong(range.start, range.endInclusive + 1)
@@ -97,30 +165,85 @@ public abstract class Random {
else -> nextLong() else -> nextLong()
} }
/**
* Gets the next random [Boolean] value.
*/
public open fun nextBoolean(): Boolean = nextBits(1) != 0 public open fun nextBoolean(): Boolean = nextBits(1) != 0
/**
* Gets the next random [Double] value uniformly distributed between 0 (inclusive) and 1 (exclusive).
*/
public open fun nextDouble(): Double = (nextBits(26).toLong().shl(27) + nextBits(27)) / (1L shl 53).toDouble() public open fun nextDouble(): Double = (nextBits(26).toLong().shl(27) + nextBits(27)) / (1L shl 53).toDouble()
/**
* Gets the next random non-negative `Double` from the random number generator not greater than the specified [bound].
*
* Generates a `Double` random value uniformly distributed between 0 (inclusive) and [bound] (exclusive).
*
* @throws IllegalArgumentException if [bound] is negative or zero.
*/
public open fun nextDouble(bound: Double): Double = nextDouble(0.0, bound)
/**
* Gets the next random `Double` from the random number generator in the specified range.
*
* Generates a `Double` random value uniformly distributed between the specified [origin] (inclusive) and the specified [bound] (exclusive).
*
* [origin] and [bound] must be finite otherwise the behavior is unspecified.
*
* @throws IllegalArgumentException if [origin] is greater than or equal to [bound].
*/
public open fun nextDouble(origin: Double, bound: Double): Double {
checkRangeBounds(origin, bound)
return (origin + nextDouble() * (bound - origin)).let { if (it >= bound) bound.nextDown() else it }
}
/**
* Gets the next random [Float] value uniformly distributed between 0 (inclusive) and 1 (exclusive).
*/
public open fun nextFloat(): Float = nextBits(24) / (1 shl 24).toFloat() 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 * Fills a subrange of the specified byte [array] starting from [fromIndex] inclusive and ending [toIndex] exclusive
* with random bytes.
*
* @return [array] with the subrange filled with random bytes.
*/
public open fun nextBytes(array: ByteArray, fromIndex: Int = 0, toIndex: Int = array.size): ByteArray {
require(fromIndex in 0..array.size && toIndex in 0..array.size) { "fromIndex ($fromIndex) or toIndex ($toIndex) are out of range: 0..${array.size}." }
require(fromIndex <= toIndex) { "fromIndex ($fromIndex) must be not greater than toIndex ($toIndex)." }
var v: Int = 0 val steps = (toIndex - fromIndex) / 4
var bits: Int = 0
for (i in startIndex until endIndex) { var position = fromIndex
if (bits == 0) { repeat(steps) {
bits = if (endIndex - i >= 4) 32 else (endIndex - i) * 8 val v = nextInt()
v = nextBits(bits) array[position] = v.toByte()
} array[position + 1] = v.ushr(8).toByte()
array[i] = v.toByte() array[position + 2] = v.ushr(16).toByte()
v = v ushr 8 array[position + 3] = v.ushr(24).toByte()
bits -= 8 position += 4
} }
val remainder = toIndex - position
val vr = nextBits(remainder * 8)
for (i in 0 until remainder) {
array[position + i] = vr.ushr(i * 8).toByte()
}
return array return array
} }
/**
* Fills the specified byte [array] with random bytes and returns it.
*
* @return [array] filled with random bytes.
*/
public open fun nextBytes(array: ByteArray): ByteArray = nextBytes(array, 0, array.size) public open fun nextBytes(array: ByteArray): ByteArray = nextBytes(array, 0, array.size)
/**
* Creates a byte array of the specified [size], filled with random bytes.
*/
public open fun nextBytes(size: Int): ByteArray = nextBytes(ByteArray(size)) public open fun nextBytes(size: Int): ByteArray = nextBytes(ByteArray(size))
// TODO: UInt, ULong, UByteArray // TODO: UInt, ULong, UByteArray
@@ -128,7 +251,7 @@ public abstract class Random {
/** /**
* The default random number generator. * The default random number generator.
* *
* * On JVM this generator is thread-safe, its methods can be invoked from multiple threads.
* *
* @sample samples.random.Randoms.defaultRandom * @sample samples.random.Randoms.defaultRandom
*/ */
@@ -136,17 +259,40 @@ public abstract class Random {
private var _defaultRandom: Random? = null private var _defaultRandom: Random? = null
private val defaultRandom: Random private val defaultRandom: Random
get() = _defaultRandom ?: defaultPlatformRandom().also { _defaultRandom = it } get() = _defaultRandom ?: initialize()
private fun initialize(): Random = defaultPlatformRandom().also { _defaultRandom = it }
override fun nextBits(bitCount: Int): Int = defaultRandom.nextBits(bitCount) override fun nextBits(bitCount: Int): Int = defaultRandom.nextBits(bitCount)
override fun nextInt(): Int = defaultRandom.nextInt()
override fun nextInt(bound: Int): Int = defaultRandom.nextInt(bound)
override fun nextInt(origin: Int, bound: Int): Int = defaultRandom.nextInt(origin, bound)
override fun nextInt(range: IntRange): Int = defaultRandom.nextInt(range)
// TODO: Override others override fun nextLong(): Long = defaultRandom.nextLong()
override fun nextLong(bound: Long): Long = defaultRandom.nextLong(bound)
override fun nextLong(origin: Long, bound: Long): Long = defaultRandom.nextLong(origin, bound)
override fun nextLong(range: LongRange): Long = defaultRandom.nextLong(range)
internal fun checkRangeBounds(origin: Long, bound: Long) { override fun nextBoolean(): Boolean = defaultRandom.nextBoolean()
if (origin >= bound) throw IllegalArgumentException("Random range is empty: [$origin, $bound)")
}
override fun nextDouble(): Double = defaultRandom.nextDouble()
override fun nextDouble(bound: Double): Double = defaultRandom.nextDouble(bound)
override fun nextDouble(origin: Double, bound: Double): Double = defaultRandom.nextDouble(origin, bound)
override fun nextFloat(): Float = defaultRandom.nextFloat()
override fun nextBytes(array: ByteArray): ByteArray = defaultRandom.nextBytes(array)
override fun nextBytes(size: Int): ByteArray = defaultRandom.nextBytes(size)
override fun nextBytes(array: ByteArray, fromIndex: Int, toIndex: Int): ByteArray = defaultRandom.nextBytes(array, fromIndex, toIndex)
internal fun checkRangeBounds(origin: Int, bound: Int) = require(bound > origin) { boundsErrorMessage(origin, bound) }
internal fun checkRangeBounds(origin: Long, bound: Long) = require(bound > origin) { boundsErrorMessage(origin, bound) }
internal fun checkRangeBounds(origin: Double, bound: Double) = require(bound > origin) { boundsErrorMessage(origin, bound) }
private fun boundsErrorMessage(origin: Any, bound: Any) = "Random range is empty: [$origin, $bound)."
} }
} }
@@ -5,6 +5,9 @@
package kotlin.random package kotlin.random
/**
* Random number generator, algorithm "xorwow" from p. 5 of Marsaglia, "Xorshift RNGs"
*/
internal class XorWowRandom internal constructor( internal class XorWowRandom internal constructor(
private var s0: Int, private var s0: Int,
private var s1: Int, private var s1: Int,
@@ -20,7 +23,6 @@ internal class XorWowRandom internal constructor(
require((s0 or s1 or s2 or s3) != 0) { "Initial state must have at least one non-zero element" } 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 { override fun nextInt(): Int {
var t = s3 var t = s3
t = t xor (t shr 2) t = t xor (t shr 2)
@@ -3881,6 +3881,8 @@ public abstract class kotlin/random/Random {
public fun nextBytes ([BII)[B public fun nextBytes ([BII)[B
public static synthetic fun nextBytes$default (Lkotlin/random/Random;[BIIILjava/lang/Object;)[B public static synthetic fun nextBytes$default (Lkotlin/random/Random;[BIIILjava/lang/Object;)[B
public fun nextDouble ()D public fun nextDouble ()D
public fun nextDouble (D)D
public fun nextDouble (DD)D
public fun nextFloat ()F public fun nextFloat ()F
public fun nextInt ()I public fun nextInt ()I
public fun nextInt (I)I public fun nextInt (I)I
@@ -3894,6 +3896,22 @@ public abstract class kotlin/random/Random {
public final class kotlin/random/Random$Companion : kotlin/random/Random { public final class kotlin/random/Random$Companion : kotlin/random/Random {
public fun nextBits (I)I public fun nextBits (I)I
public fun nextBoolean ()Z
public fun nextBytes (I)[B
public fun nextBytes ([B)[B
public fun nextBytes ([BII)[B
public fun nextDouble ()D
public fun nextDouble (D)D
public fun nextDouble (DD)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/RandomKt { public final class kotlin/random/RandomKt {