diff --git a/libraries/stdlib/js/src/kotlin/random/PlatformRandom.kt b/libraries/stdlib/js/src/kotlin/random/PlatformRandom.kt index 4653142e8ce..341a8600806 100644 --- a/libraries/stdlib/js/src/kotlin/random/PlatformRandom.kt +++ b/libraries/stdlib/js/src/kotlin/random/PlatformRandom.kt @@ -5,10 +5,8 @@ package kotlin.random -import kotlin.math.pow - internal actual fun defaultPlatformRandom(): Random = - XorWowRandom((js("Math").random().unsafeCast() * (2.0.pow(32.0))).toInt()) + Random(js("(Math.random() * Math.pow(2, 32)) | 0").unsafeCast()) internal actual fun fastLog2(value: Int): Int { diff --git a/libraries/stdlib/samples/test/samples/random/randoms.kt b/libraries/stdlib/samples/test/samples/random/randoms.kt index aab1eeb90ec..1637b336fa3 100644 --- a/libraries/stdlib/samples/test/samples/random/randoms.kt +++ b/libraries/stdlib/samples/test/samples/random/randoms.kt @@ -28,7 +28,7 @@ class Randoms { val randomValues1 = getRandomList(Random(42)) // prints the same sequence every time - assertPrints(randomValues1, "[44, 34, 69, 67, 22, 16, 67, 45, 95, 10]") + assertPrints(randomValues1, "[33, 40, 41, 2, 41, 32, 21, 40, 69, 87]") val randomValues2 = getRandomList(Random(42)) // random with the same seed produce the same sequence @@ -36,6 +36,6 @@ class Randoms { val randomValues3 = getRandomList(Random(0)) // random with another seed produce another sequence - assertPrints(randomValues3, "[20, 63, 41, 28, 0, 99, 35, 42, 72, 13]") + assertPrints(randomValues3, "[14, 48, 57, 67, 82, 7, 61, 27, 14, 59]") } } \ No newline at end of file diff --git a/libraries/stdlib/src/kotlin/random/Random.kt b/libraries/stdlib/src/kotlin/random/Random.kt index 9f7be663d18..f7ff0507328 100644 --- a/libraries/stdlib/src/kotlin/random/Random.kt +++ b/libraries/stdlib/src/kotlin/random/Random.kt @@ -299,7 +299,7 @@ public abstract class Random { * @sample samples.random.Randoms.seededRandom */ @SinceKotlin("1.3") -public fun Random(seed: Int): Random = XorWowRandom(seed) +public fun Random(seed: Int): Random = XorWowRandom(seed, seed.shr(31)) /** * Returns a repeatable random number generator seeded with the given [seed] `Long` value. @@ -309,7 +309,7 @@ public fun Random(seed: Int): Random = XorWowRandom(seed) * @sample samples.random.Randoms.seededRandom */ @SinceKotlin("1.3") -public fun Random(seed: Long): Random = XorWowRandom(seed) +public fun Random(seed: Long): Random = XorWowRandom(seed.toInt(), seed.shr(32).toInt()) internal expect fun defaultPlatformRandom(): Random diff --git a/libraries/stdlib/src/kotlin/random/XorWowRandom.kt b/libraries/stdlib/src/kotlin/random/XorWowRandom.kt index aceb034f044..f6d398643b6 100644 --- a/libraries/stdlib/src/kotlin/random/XorWowRandom.kt +++ b/libraries/stdlib/src/kotlin/random/XorWowRandom.kt @@ -6,36 +6,44 @@ package kotlin.random /** - * Random number generator, algorithm "xorwow" from p. 5 of Marsaglia, "Xorshift RNGs" + * Random number generator, algorithm "xorwow" from p. 5 of Marsaglia, "Xorshift RNGs". + * + * Cycles after 2^160 * (2^32-1) repetitions. + * + * See http://www.jstatsoft.org/v08/i14/paper for details. */ -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 +internal class XorWowRandom +internal constructor( + private var x: Int, + private var y: Int, + private var z: Int, + private var w: Int, + private var v: Int, + private var addend: 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) + internal constructor(seed1: Int, seed2: Int) : + this(seed1, seed2, 0, 0, seed1.inv(), (seed1 shl 10) xor (seed2 ushr 4)) init { - require((s0 or s1 or s2 or s3) != 0) { "Initial state must have at least one non-zero element" } + require((x or y or z or w or v) != 0) { "Initial state must have at least one non-zero element." } + + // some trivial seeds can produce several values with zeroes in upper bits, so we discard first 64 + repeat(64) { nextInt() } } 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 + var t = x + t = t xor (t ushr 2) + x = y + y = z + z = w + val v0 = v + w = v0 + t = (t xor (t shl 1)) xor v0 xor (v0 shl 4) + v = t + addend += 362437 + return t + addend } override fun nextBits(bitCount: Int): Int = nextInt() ushr (32 - bitCount) diff --git a/libraries/stdlib/test/random/XorWowRandomImplTest.kt b/libraries/stdlib/test/random/XorWowRandomImplTest.kt new file mode 100644 index 00000000000..64ff79a82ed --- /dev/null +++ b/libraries/stdlib/test/random/XorWowRandomImplTest.kt @@ -0,0 +1,27 @@ +/* + * 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 test.random + +import kotlin.math.pow +import kotlin.random.* +import kotlin.test.* + +class XorWowRandomImplTest { + @Test + fun predefinedSequence() { + val seed = 1 + val addend = (seed shl 10) xor (seed ushr 4) + + @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") + val random: Random = XorWowRandom(seed, 0, 0, 0, 0, addend) + + // differs from reference 0.8178000247146859 because of different double mixing algorithm + assertEquals(0.817799582443095, random.nextDouble()) + + assertEquals(0.8407576507888734, random.nextBits(31) / (2.0.pow(31))) + assertEquals(533150816, random.nextInt()) + } +} \ No newline at end of file