-
-
Save nicklockwood/cf1fde3eb24602883c17f94e63e490bc to your computer and use it in GitHub Desktop.
struct RNG: RandomNumberGenerator { | |
let modulus: UInt64 = 233_280 | |
let multiplier: UInt64 = 9301 | |
let increment: UInt64 = 49297 | |
var seed: UInt64 = 0 | |
mutating func next() -> UInt64 { | |
seed = (seed * multiplier + increment) % modulus | |
return seed | |
} | |
} | |
var rng = RNG(seed: 1000) | |
var numbers = [0, 1, 2, 3, 4, 5, 6] | |
print("### next() ###") | |
for _ in 0 ..< 5 { | |
print(rng.next()) | |
} | |
print("### next(upperBound:) ###") | |
for _ in 0 ..< 5 { | |
print(rng.next(upperBound: UInt(numbers.count))) | |
} | |
print("### random(in:) ###") | |
for _ in 0 ..< 5 { | |
print(numbers[Int.random(in: numbers.indices, using: &rng)]) | |
} | |
print("### randomElement() ###") | |
for _ in 0 ..< 5 { | |
print(numbers.randomElement(using: &rng)!) | |
} |
When I run this in Xcode 10.1 I get the expected results:
### next() ###
19097
144414
17671
178148
16005
### next(upperBound:) ###
6
3
0
1
5
### random(in:) ###
6
0
4
3
1
### randomElement() ###
5
5
4
1
3
@tempelmann it works for me too in Xcode 11.3.1.
I think this was a regression, possibly introduced by swiftlang/swift#25286
This is very frustrating, because it's a pretty standard expectation that an RNG will use the low order bits when selecting a number in a small range, and while using the full 64-bit range may be more correct by some mathematical standard, it's a huge breaking change to have implemented with little or no fanfare.
Actually, if you look at https://en.wikipedia.org/wiki/Linear_congruential_generator, at the table column "output bits of seed", you'll see that many PRNGs have the useable bits in the upper area. That's why this Swift class should have a function that returns that "useable range", e.g. as a divisor and a max value, and the upperbound function should use that info to normalize the result from next()
first, before then applying the reduction for upperBound. It's generally unlikely to expect that all 64 bits of even the standard (system) RNG are all useable.
Also, I find the name "upperBound" misleading. Is that term used throughout Swift for "max value plus + 1"? To me, upper bound means max value, without plus 1.
The upperBound usage is consistent with the rest of Swift.
Output: