Skip to content

Commit

Permalink
Split Seed on interface and implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
LMnet committed Sep 20, 2019
1 parent bfff5b1 commit 5df44f4
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 173 deletions.
20 changes: 14 additions & 6 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ val scalaMajorVersion = SettingKey[Int]("scalaMajorVersion")

scalaVersionSettings

lazy val versionNumber = "1.14.2"
lazy val versionNumber = "1.14.1-2gis-market-1"

lazy val isRelease = false
lazy val isRelease = true

lazy val travisCommit = Option(System.getenv().get("TRAVIS_COMMIT"))

Expand Down Expand Up @@ -112,11 +112,19 @@ lazy val sharedSettings = MimaSettings.settings ++ scalaVersionSettings ++ Seq(
else Set("org.scalacheck" %%% "scalacheck" % "1.14.0")
},

// publishTo := {
// val nexus = "https://oss.sonatype.org/"
// val (name, path) = if (isSnapshot.value) ("snapshots", "content/repositories/snapshots")
// else ("releases", "service/local/staging/deploy/maven2")
// Some(name at nexus + path)
// },

publishTo := {
val nexus = "https://oss.sonatype.org/"
val (name, path) = if (isSnapshot.value) ("snapshots", "content/repositories/snapshots")
else ("releases", "service/local/staging/deploy/maven2")
Some(name at nexus + path)
val artifactory = "https://artifactory.web-staging.2gis.ru/artifactory/"
if (version.value.trim.endsWith("SNAPSHOT"))
Some("snapshots" at artifactory + "libs-snapshot-local")
else
Some("releases" at artifactory + "libs-release-local")
},

publishMavenStyle := true,
Expand Down
195 changes: 28 additions & 167 deletions src/main/scala/org/scalacheck/rng/Seed.scala
Original file line number Diff line number Diff line change
@@ -1,185 +1,46 @@
package org.scalacheck
package rng

import scala.annotation.tailrec
import scala.util.Try

/**
* Simple RNG by Bob Jenkins:
*
* http://burtleburtle.net/bob/rand/smallprng.html
*/
sealed abstract class Seed extends Serializable {
protected val a: Long
protected val b: Long
protected val c: Long
protected val d: Long

/**
* Generate a Base-64 representation of this seed.
*
* Given a seed, this method will return a String with 44
* characters, according to the web-safe Base-64 specification
* (i.e. using minus (-) and underscore (_) in addition to
* alphanumeric characters).
*
* The 256-bit seed is serialized as a little-endian array of 64-bit
* Long values. Strings produced by this method are guaranteed to be
* parseable by the Seed.fromBase64 method.
*/
def toBase64: String = {
def enc(x: Long): Char = Seed.Alphabet((x & 0x3f).toInt)
val chars = new Array[Char](44)
def encode(x: Long, shift: Int, i: Int, rest: List[Long]): String =
if (shift < 58) {
chars(i) = enc(x >>> shift)
encode(x, shift + 6, i + 1, rest)
} else rest match {
case Nil =>
chars(i) = enc(x >>> 60)
chars(i + 1) = '='
new String(chars)
case y :: ys =>
val sh = 64 - shift
chars(i) = enc((x >>> shift) | (y << sh))
encode(y, (6 - sh) % 6, i + 1, ys)
}
encode(a, 0, 0, b :: c :: d :: Nil)
}

/** Generate the next seed in the RNG's sequence. */
def next: Seed = {
import java.lang.Long.rotateLeft
val e = a - rotateLeft(b, 7)
val a1 = b ^ rotateLeft(c, 13)
val b1 = c + rotateLeft(d, 37)
val c1 = d + e
val d1 = e + a
Seed(a1, b1, c1, d1)
}

/** Reseed the RNG using the given Long value. */
def reseed(n: Long): Seed = {
val n0 = ((n >>> 32) & 0xffffffff)
val n1 = (n & 0xffffffff)
var i = 0
var seed: Seed = Seed(a ^ n0, b ^ n1, c, d)
while(i < 16) { seed = seed.next; i += 1 }
seed
}

/**
* This is a quick way of deterministically sliding this RNG to a
* different part of the PRNG sequence.
*
* We use this as an easy way to "split" the RNG off into a new part
* of the sequence. We want to do this in situations where we've
* already called .next several times, and we want to avoid
* repeating those numbers while preserving determinism.
*/
def slide: Seed = {
val (n, s) = long
s.reseed(n)
}

/**
* Generates a Long value.
*
* The values will be uniformly distributed. */
def long: (Long, Seed) = (d, next)

/**
* Generates a Double value.
*
* The values will be uniformly distributed, and will be contained
* in the interval [0.0, 1.0). */
def double: (Double, Seed) = ((d >>> 11) * 1.1102230246251565e-16, next)
trait Seed extends Serializable {
def toBase64: String
def next: Seed
def reseed(n: Long): Seed
def slide: Seed
def long: (Long, Seed)
def double: (Double, Seed)
}

object Seed {

private case class apply(a: Long, b: Long, c: Long, d: Long) extends Seed {
override def toString: String = s"""Seed.fromBase64("$toBase64")"""
}

/** Generate a deterministic seed. */
def apply(s: Long): Seed = {
var i = 0
var seed: Seed = Seed(0xf1ea5eed, s, s, s)
while (i < 20) { seed = seed.next; i += 1 }
seed
}

/**
* Generate a seed directly from four Long values.
*
* Warning: unlike Seed.apply(Long), this method just directly
* constructs a seed from the four Long values. Prefer using
* `Seed(Long)` if you aren't sure whether these will be good seed
* values.
*/
def fromLongs(a: Long, b: Long, c: Long, d: Long): Seed =
apply(a, b, c, d)
def apply(s: Long): Seed = SimpleRngSeed(s)

/**
* Alphabet of characters used by the `toBase64` method.
*
* Since we're using the web-safe Base-64 specification, we are
* using minus (-) and underscore(_) in addition to the alphanumeric
* characters.
*/
private[scalacheck] final val Alphabet: Array[Char] =
((0 until 26).map(i => ('A' + i).toChar) ++
(0 until 26).map(i => ('a' + i).toChar) ++
(0 until 10).map(i => ('0' + i).toChar) ++
Vector('-', '_')).toArray
* Generate a seed directly from four Long values.
*
* Warning: unlike Seed.apply(Long), this method just directly
* constructs a seed from the four Long values. Prefer using
* `Seed(Long)` if you aren't sure whether these will be good seed
* values.
*/
def fromLongs(a: Long, b: Long, c: Long, d: Long): Seed = new SimpleRngSeed(a, b, c, d)

/**
* Parse a Base-64 encoded seed, returning a Seed value.
*
* This method requires the exact format produced by `toBase64`
* (i.e. a 44-character string using the web-safe Base-64
* alphabet). Other encodings must produce precisely the same
* outputs to be supported.
*
* This method will throw an IllegalArgumentException if parsing
* fails.
*/
def fromBase64(s: String): Try[Seed] = {
def fail(s: String): Nothing = throw new IllegalArgumentException(s)

def dec(c: Char): Long =
if ('A' <= c && c <= 'Z') (c - 'A').toLong
else if ('a' <= c && c <= 'z') ((c - 'a') + 26).toLong
else if ('0' <= c && c <= '9') ((c - '0') + 52).toLong
else if (c == '-') 62L
else if (c == '_') 63L
else fail(s"illegal Base64 character: $c")

val longs = new Array[Long](4)
@tailrec def decode(x: Long, shift: Int, i: Int, j: Int): Seed =
if (i >= 43) {
Seed.fromLongs(longs(0), longs(1), longs(2), longs(3))
} else {
val b = dec(s.charAt(i))
if (shift < 58) {
decode(x | (b << shift), shift + 6, i + 1, j)
} else {
longs(j) = x | (b << shift)
val sh = 64 - shift
decode(b >>> sh, 6 - sh, i + 1, j + 1)
}
}

Try {
if (s.length != 44) fail(s"wrong Base64 length: $s")
if (s.charAt(43) != '=') fail(s"wrong Base64 format: $s")
if (s.charAt(42) == '=') fail(s"wrong Base64 format: $s")
decode(0L, 0, 0, 0)
}
}
* Parse a Base-64 encoded seed, returning a Seed value.
*
* This method requires the exact format produced by `toBase64`
* (i.e. a 44-character string using the web-safe Base-64
* alphabet). Other encodings must produce precisely the same
* outputs to be supported.
*
* This method will throw an IllegalArgumentException if parsing
* fails.
*/
def fromBase64(s: String): Try[Seed] = SimpleRngSeed.fromBase64(s)

/** Generate a random seed. */
def random(): Seed = apply(scala.util.Random.nextLong)
def random(): Seed = SimpleRngSeed.random()

}

0 comments on commit 5df44f4

Please sign in to comment.