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() {
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(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)
override fun nextDouble(bound: Double): Double = impl.nextDouble(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 {
// TODO: not so fast, make faster
require(value > 0)
var v = value
var log = -1
while (v != 0) {
@@ -8,13 +8,19 @@ package kotlin.random
import kotlin.UnsupportedOperationException
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")
public fun Random.asJavaRandom(): java.util.Random =
(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")
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 nextInt(): Int = impl.nextInt()
override fun nextInt(bound: Int): Int = impl.nextInt(bound)
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 }
override fun nextBytes(array: ByteArray): ByteArray = array.also { impl.nextBytes(it) }
}
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 nextDouble(): Double = impl.nextDouble()
override fun setSeed(seed: Long) {
throw UnsupportedOperationException("Setting seed is not supported")
}
override fun nextBytes(bytes: ByteArray) {
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
import kotlin.*
import kotlin.math.nextDown
/**
* An abstract class that is implemented by random number generator algorithms.
@@ -18,12 +19,43 @@ import kotlin.*
*/
@SinceKotlin("1.3")
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
/**
* 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)
/**
* 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)
/**
* 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 {
checkRangeBounds(origin.toLong(), bound.toLong())
checkRangeBounds(origin, bound)
val n = bound - origin
if (n > 0 || n == Int.MIN_VALUE) {
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 {
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()
}
/**
* 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()
/**
* 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)
/**
* 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 {
checkRangeBounds(origin, bound)
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 {
// range.isEmpty() -> throw IllegalArgumentException("Cannot get random in empty range: $range")
range.last < Long.MAX_VALUE -> nextLong(range.start, range.endInclusive + 1)
@@ -97,30 +165,85 @@ public abstract class Random {
else -> nextLong()
}
/**
* Gets the next random [Boolean] value.
*/
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()
/**
* 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 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
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
val steps = (toIndex - fromIndex) / 4
var position = fromIndex
repeat(steps) {
val v = nextInt()
array[position] = v.toByte()
array[position + 1] = v.ushr(8).toByte()
array[position + 2] = v.ushr(16).toByte()
array[position + 3] = v.ushr(24).toByte()
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
}
/**
* 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)
/**
* Creates a byte array of the specified [size], filled with random bytes.
*/
public open fun nextBytes(size: Int): ByteArray = nextBytes(ByteArray(size))
// TODO: UInt, ULong, UByteArray
@@ -128,7 +251,7 @@ public abstract class Random {
/**
* 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
*/
@@ -136,17 +259,40 @@ public abstract class Random {
private var _defaultRandom: Random? = null
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 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) {
if (origin >= bound) throw IllegalArgumentException("Random range is empty: [$origin, $bound)")
}
override fun nextBoolean(): Boolean = defaultRandom.nextBoolean()
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
/**
* Random number generator, algorithm "xorwow" from p. 5 of Marsaglia, "Xorshift RNGs"
*/
internal class XorWowRandom internal constructor(
private var s0: 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" }
}
/* Algorithm "xorwow" from p. 5 of Marsaglia, "Xorshift RNGs" */
override fun nextInt(): Int {
var t = s3
t = t xor (t shr 2)
@@ -3881,6 +3881,8 @@ public abstract class kotlin/random/Random {
public fun nextBytes ([BII)[B
public static synthetic fun nextBytes$default (Lkotlin/random/Random;[BIIILjava/lang/Object;)[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
@@ -3894,6 +3896,22 @@ public abstract class kotlin/random/Random {
public final class kotlin/random/Random$Companion : kotlin/random/Random {
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 {