The reason I mentioned upthread that a seedable random number generator is one of my top wishes for the standard library is pretty much this: I am not an expert in this area either.
Randomized testing (property-based, metamorphic, etc) is an extremely important test strategy and should be supported directly by Swift without requiring personal expertise in random number generators or a 3rd party library.
To add another voice to this chorus, I’m about as close to an expert as a non-expert can get.
I have a degree in math, I’ve been reading about RNGs (and studying their implementations) as a hobby for years. I’m currently in the process of rewriting the Swift standard library’s floating-point random number generator to do what it says on the tin (generate any value in the range with correct probability). Just today I wrote a proof-of-concept reimplementation of RandomNumberGenerator.next(upperBound:) -> T that’s 10-20% faster than the standard library’s version for the hardest cases, and equal speed for the general case.
And despite all of that, I do not know how to properly choose an RNG.
I have a general hazy feeling that Xoroshiro256++ (or **) is probably where one wants to be for most non-cryptographic applications, but I can’t back that up with any solid rationale.
Now, I don’t necessarily need a repeatable RNG to be in the standard library, but it would certainly be nice to have a generally-agreed-on-by-experts option available. Putting it in the Numerics package would also work just fine for me.
Certainly non-catastrophic ones exist. The entropy in the low-order bits is prone to be not very good, but that can be fine, depending on use. PCG, which is one of the families that I consider to be worth using, pairs a simple LCG with an "output function" to overcome this limitation (jrand48 effectively does, too, but with a much less good output function--dropping the low-order 16 bits--but it's still better than nothing). The nice thing about this approach is that the only dependency between iterations is the LCG, so on a modern wide core, you get equivalent performance to a simple LCG, but with better statistical properties.
To give you a little reassurance, based on what you've told me about your use case, what you're doing is fine for now.
@nevin: yeah, the recent Xoroshiro variants and PCG-family are the two most well-known "pretty good choices" (largely because of the ongoing spat between their creators--no such thing as bad publicity ).
Yeah, just to be totally clear, I'm definitely not saying "there's nothing to be done here." There's a very real and very important feature that you're asking for.
I have an implementation of Xoroshiro256** in Swift, but I'm not sure if it's ready for public consumption. I haven't even bothered commenting it yet. If it'll help you, here's the stripped down bare to minimum version:
Xoroshiro256StarStar.swift
private func rotl(_ x: UInt64, _ k: UInt64) -> UInt64 {
return (x << k) | (x >> (64 - k))
}
struct Xoroshiro256StarStar: RandomNumberGenerator {
typealias State = (UInt64, UInt64, UInt64, UInt64)
var state: State
init<Source: RandomNumberGenerator>(from source: inout Source) {
repeat {
state = (source.next(), source.next(), source.next(), source.next())
} while state == (0, 0, 0, 0)
}
init() {
var entropy = SystemRandomNumberGenerator()
self.init(from: &entropy)
}
init?(state: State) {
guard state != (0, 0, 0, 0) else { return nil }
self.state = state
}
mutating func next() -> UInt64 {
let result = rotl(state.1 &* 5, 7) &* 9
let t = state.1 << 17
state.2 ^= state.0
state.3 ^= state.1
state.1 ^= state.2
state.0 ^= state.3
state.2 ^= t
state.3 = rotl(state.3, 45)
return result
}
}
I’ll second this. I’ve used Matt Gallagher’s RNGs when needed in the past, but I think this is the kind of thing that should be in the library. Even if we choose a not-particularly-good generator (and say so in the docs), anything with seed capabilities would help already.