Deterministic "randomness" in Swift

Is there any easy way to create reproducible pseudo-random numbers with Swift currently. It would be useful for e.g. unit testing where a large amount of unstructured data is desired, but you want any failures to be reproducible and debuggable. Currently, my solution has been to call po array.shuffle().description from the debugger and then paste that into my test code as a constant, but I would love it if there were, some other solution such as a SeededRandomNumberGenerator type. Is there a better solution to this that exists today?

4 Likes

Some thoughts which might be helpful or not:

Cool, didn't realize that SWIFT_DETERMINISTIC_HASHING made all other randomness deterministic as well. I still think that having it be a compiler flag is a bit harsh since there may be production environments where you want the security of randomly seeded hashing in one part of the program, and deterministic-but-random-looking numbers in another. Importing GamePlayKit does seem to be the best solution, but (a) is relatively undiscoverable compared to SystemRandomNumberGenerator in the standard library, and (b) importing a whole framework designed for games into a program that may have nothing to do with games just for the random number generator seems like overkill.

1 Like

I did not re-read all of SE-0202: Random Unification, that thread my contain more information about why the standard library does not provide some seedable PRNG.

1 Like

Ah, yeah looks like there's some good discussion in there. Thanks!

If you need a seedable PRNG, then here's a minimal implementation of XoroShiro256**:

protocol PseudoRandomGenerator: RandomNumberGenerator {
    associatedtype State
    init(seed: State)
    init<Source: RandomNumberGenerator>(from source: inout Source)
}

extension PseudoRandomGenerator {
    init() {
        var source = SystemRandomNumberGenerator()
        self.init(from: &source)
    }
}

private func rotl(_ x: UInt64, _ k: UInt64) -> UInt64 {
    return (x << k) | (x >> (64 &- k))
}

struct Xoroshiro256StarStar: PseudoRandomGenerator {
    typealias State = (UInt64, UInt64, UInt64, UInt64)
    var state: State

    init(seed: State) {
        precondition(seed != (0, 0, 0, 0))
        state = seed
    }

    init<Source: RandomNumberGenerator>(from source: inout Source) {
        repeat {
            state = (source.next(), source.next(), source.next(), source.next())
        } while state == (0, 0, 0, 0)
    }

    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
    }
}
2 Likes

SWIFT_DETERMINISTIC_HASHING does not affect any functionality brought from SE-0202. That environment variable only affects hashing. The stdlib does not currently include any prng for seedable reproduction, but it is something that is open for discussion for a future release. For now, the best way to achieve seedable randomness is to define your own prng like @Nobody1707 presented.

7 Likes

In the Swift for TensorFlow deep learning library we defined a SeedableRandomNumberGenerator protocol and a few PRNGs: PhiloxRandomNumberGenerator, ThreefryRandomNumberGenerator, and ARC4RandomNumberGenerator.

2 Likes

Seedable PRNGs are also defined in the awesome SwiftCheck library.

1 Like

Recently on pointfree.co they talk about how to do exactly this.

https://www.pointfree.co/episodes/ep48-predictable-randomness-part-2

Previous Episodes about it

https://www.pointfree.co/episodes/ep47-predictable-randomness-part-1

https://www.pointfree.co/episodes/ep30-composable-randomness

https://www.pointfree.co/blog/posts/19-random-zalgo-generator

PCG family of algorithms needs more love. http://www.pcg-random.org/
It is statistically good and simple to implement.

2 Likes

I have wrapped PCG into a package here: https://github.com/paiv/swift-pcg-random

4 Likes

Swift's GameKit has seeded random capabilities.

GameKit is not a Swift framework, so it doesnโ€™t help in the general case.

It would be quite useful to have deterministic randomness during development. I remember I was chasing a bug and succeeded only when I replaced all those various "random" calls to my wrappers which I based on some simple pseudorandom generator and thus removed the main sources of randomness (*) to get the reproducible case I was finally able debug and fix. This approach has it's limitations (third party components or the system itself can have calls to random and it's not easy to patch that).

(*) some remaining randomness (e.g. due to timing variation of code execution or due to effectively "random" memory addresses you are getting back from malloc) is inevitable.

There's really only two things missing required for a good general PseudorandomNumberGenerator protocol. Adding a fill method to the RandomNumberGenerator protocol, and the ability to require types to be trivially copyable.

A guarantee that random(in:using:) and friends will produce the same results when the given RNG produces the same sequence of random bits regardless of endianness would be nice, but we might already have that.

What? It's a built in apple Import. Easy to add to any project. It's like saying "I wish Sin and CoSin were available in swift", then someone responding "they are if you use Import Math" and then someone like you responding, "yea but that means you have use import". Sorry. I'm salty today.

Swift is available on platforms where GameKit doesn't exist, so a GameKit-based solution isn't really applicable to solving this problem for the Swift language.

2 Likes

Here's another page explaining how to use Import GameplayKit to get seeded random numbers.

Ah. Gotcha. Bummer