diff --git a/libraries/stdlib/jdk7/src/kotlin/internal/jdk7/JDK7PlatformImplementations.kt b/libraries/stdlib/jdk7/src/kotlin/internal/jdk7/JDK7PlatformImplementations.kt index 5cce9d93f94..1146bbdf609 100644 --- a/libraries/stdlib/jdk7/src/kotlin/internal/jdk7/JDK7PlatformImplementations.kt +++ b/libraries/stdlib/jdk7/src/kotlin/internal/jdk7/JDK7PlatformImplementations.kt @@ -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() + } diff --git a/libraries/stdlib/jdk7/src/kotlin/random/jdk7/PlatformThreadLocalRandom.kt b/libraries/stdlib/jdk7/src/kotlin/random/jdk7/PlatformThreadLocalRandom.kt new file mode 100644 index 00000000000..2cb5071eb39 --- /dev/null +++ b/libraries/stdlib/jdk7/src/kotlin/random/jdk7/PlatformThreadLocalRandom.kt @@ -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) +} \ 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 new file mode 100644 index 00000000000..0b6608f6d4f --- /dev/null +++ b/libraries/stdlib/js/src/kotlin/random/PlatformRandom.kt @@ -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() * (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 +} \ No newline at end of file diff --git a/libraries/stdlib/jvm/src/kotlin/internal/PlatformImplementations.kt b/libraries/stdlib/jvm/src/kotlin/internal/PlatformImplementations.kt index 5679f905a27..75b2274a66b 100644 --- a/libraries/stdlib/jvm/src/kotlin/internal/PlatformImplementations.kt +++ b/libraries/stdlib/jvm/src/kotlin/internal/PlatformImplementations.kt @@ -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() } diff --git a/libraries/stdlib/jvm/src/kotlin/random/PlatformRandom.kt b/libraries/stdlib/jvm/src/kotlin/random/PlatformRandom.kt new file mode 100644 index 00000000000..1ef7c73eb32 --- /dev/null +++ b/libraries/stdlib/jvm/src/kotlin/random/PlatformRandom.kt @@ -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() { + 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) + } +} + + diff --git a/libraries/stdlib/samples/test/samples/random/randoms.kt b/libraries/stdlib/samples/test/samples/random/randoms.kt new file mode 100644 index 00000000000..aab1eeb90ec --- /dev/null +++ b/libraries/stdlib/samples/test/samples/random/randoms.kt @@ -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 = + 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]") + } +} \ No newline at end of file diff --git a/libraries/stdlib/src/kotlin/random/Random.kt b/libraries/stdlib/src/kotlin/random/Random.kt new file mode 100644 index 00000000000..d7a541620a6 --- /dev/null +++ b/libraries/stdlib/src/kotlin/random/Random.kt @@ -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) + diff --git a/libraries/stdlib/src/kotlin/random/XorWowRandom.kt b/libraries/stdlib/src/kotlin/random/XorWowRandom.kt new file mode 100644 index 00000000000..c7bef332c0b --- /dev/null +++ b/libraries/stdlib/src/kotlin/random/XorWowRandom.kt @@ -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) +} 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 3c905b8f792..48ee34e5ff7 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 @@ -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 ()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