diff --git a/libraries/stdlib/jdk7/src/kotlin/random/jdk7/PlatformThreadLocalRandom.kt b/libraries/stdlib/jdk7/src/kotlin/random/jdk7/PlatformThreadLocalRandom.kt index 2cb5071eb39..4fdb91a1b58 100644 --- a/libraries/stdlib/jdk7/src/kotlin/random/jdk7/PlatformThreadLocalRandom.kt +++ b/libraries/stdlib/jdk7/src/kotlin/random/jdk7/PlatformThreadLocalRandom.kt @@ -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) } \ No newline at end of file diff --git a/libraries/stdlib/js/src/kotlin/random/PlatformRandom.kt b/libraries/stdlib/js/src/kotlin/random/PlatformRandom.kt index 0b6608f6d4f..4653142e8ce 100644 --- a/libraries/stdlib/js/src/kotlin/random/PlatformRandom.kt +++ b/libraries/stdlib/js/src/kotlin/random/PlatformRandom.kt @@ -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) { diff --git a/libraries/stdlib/jvm/src/kotlin/random/PlatformRandom.kt b/libraries/stdlib/jvm/src/kotlin/random/PlatformRandom.kt index 1ef7c73eb32..8b49defe453 100644 --- a/libraries/stdlib/jvm/src/kotlin/random/PlatformRandom.kt +++ b/libraries/stdlib/jvm/src/kotlin/random/PlatformRandom.kt @@ -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.") + } } diff --git a/libraries/stdlib/src/kotlin/random/Random.kt b/libraries/stdlib/src/kotlin/random/Random.kt index d7a541620a6..8102d9b18ef 100644 --- a/libraries/stdlib/src/kotlin/random/Random.kt +++ b/libraries/stdlib/src/kotlin/random/Random.kt @@ -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)." } } diff --git a/libraries/stdlib/src/kotlin/random/XorWowRandom.kt b/libraries/stdlib/src/kotlin/random/XorWowRandom.kt index c7bef332c0b..aceb034f044 100644 --- a/libraries/stdlib/src/kotlin/random/XorWowRandom.kt +++ b/libraries/stdlib/src/kotlin/random/XorWowRandom.kt @@ -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) diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index 7fa8bafb5a9..0c8f04fd27c 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -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 {