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?

2 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
    }
}
1 Like

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.

6 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
Terms of Service

Privacy Policy

Cookie Policy