Generating Random Numbers in Swift
Manny happy returns, C
The most common functions that allow generating random numbers are arc4random()
and arc4random_uniform()
. Both come from C and are pretty simple in usage. Though arc4random()
is not recommended, because it suffers from a condition called modulo bias (some numbers appear more often than others).
Int(arc4random_uniform(10)) // returns random Int between 0 and 9 Int(arc4random_uniform(10)) + 1 // returns random Int between 1 and 10
Unfortunately arc4random_uniform()
returns UInt32
, so you probably need to cast it to Int
, which is easier to work with.
You can simply use this function to get i.e. random element of array as well.
extension Array {
var random: Element? {
guard !isEmpty else { return nil }
return self[Int(arc4random_uniform(UInt32(count)))]
}
}
If you wish to get random Double
, then you have to use drand48()
, which returns floating-point number between 0.0 and 1.0.
GameplayKit
Since iOS 9 there is a cool framework called GameplayKit that also allows generating random numbers. Although the name, it works well not only in games.
import GameplayKit let randomSource = GKRandomSource.sharedRandom() randomSource.nextInt() // returns random Int between Int32.min and Int32.max randomSource.nextInt(upperBound: 10) // returns random Int between 0 and 9 randomSource.nextUniform() // returns random Float between 0.0 and 1.0 randomSource.nextBool() // returns random Bool
Looks much better than the old C function, especially when it comes to readability, but in fact it uses arc4random
function family to return values.
Roll a dice
I guess it's an example that always appears, but it also looks nice in GameplayKit.
// This is accessible through GKRandomDistribution.d6() as well let 🎲 = GKRandomDistribution(forDieWithSideCount: 6) let roll = 🎲.nextInt()
It's important to use the same distribution object's instance to get next dice roll result. If you need to roll two or more dices, then you should have an instance of GKRandomDistribution
for each of them.
Shuffled distribution
GKShuffledDistribution
is an interesting class that inherits from GKRandomDistribution
. It shapes the distribution of random numbers so that you are less likely to get repeats.
It's called fair distribution, because every number will appear an equal number of times. The shuffled distribution makes sure not to repeat any one value until it has used all of its possible values. In a six-sided dice example, if the dice rolls a 1, then another 1 won't be generated for at least five more rolls.
let 🎲 = GKShuffledDistribution.d6() (1...100).forEach { _ in print(🎲.nextInt()) } // could be: 1, 5, 2, 4, 6, 3, 1, 3, 2, 6, 4, 5, 4, 3, 6, 5, 1, 2, …
Gaussian distribution
A moment ago I said that you need three GKRandomDistribution
instances when you're rolling three dices. And that's true. But you can also use the Gaussian distribution.
In the example you get a sum of the result of rolling three six-sided dice.
let random = GKRandomSource() let 🎲 = GKGaussianDistribution(randomSource: random, lowestValue: 3, highestValue: 18) let roll = 🎲.nextInt()
Pseudo-random, not random
You need to remember that all the examples return pseudo-random numbers. They're fine most of the time, but are not cryptographically robust.
All of them uses a source (also known as seed). Sometimes visible, sometimes not. You can find it easily in GKGaussianDistribution
initialiser above, but it exists in all GKRandomDistribution
s.
What it means? All generated numbers are based on the seed. When you know a seed, then you know the whole sequence.
let seed: UInt64 = 666 let notSoRandomSource1 = GKMersenneTwisterRandomSource(seed: seed) let 🎲1 = GKRandomDistribution(randomSource: notSoRandomSource1, lowestValue: 1, highestValue: 6) let first = (1...100).map { _ in 🎲1.nextInt() } let notSoRandomSource2 = GKMersenneTwisterRandomSource(seed: seed) let 🎲2 = GKRandomDistribution(randomSource: notSoRandomSource2, lowestValue: 1, highestValue: 6) let second = (1...100).map { _ in 🎲2.nextInt() } let areTheSame = first == second // true
Does your sequence start with 6, 2, 5, 3, 2, 3, 6, 2, 2, 5 too? That's not a coincidence.
Cryptographically secure random numbers
As I said before this one is a bit more difficult, but you should definitely use it when security matters.
If you have ever used encrypted Realm database, then probably the code below is not something new for you.
var bytes = [UInt8](repeating: 0, count: 64) SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes) let key = Data(bytes: bytes)
It makes usage of Security framework and the default random number generator from the framework. These three lines, eventually give you 64 bytes data object with random numbers inside. It's close to what we need, but how to convert this to get a number?
let bytesCount = 4 var random: UInt32 = 0 var randomBytes = [UInt8](repeating: 0, count: bytesCount) SecRandomCopyBytes(kSecRandomDefault, bytesCount, &randomBytes) NSData(bytes: randomBytes, length: bytesCount) .getBytes(&random, length: bytesCount) print(random) // prints UInt32 random number
The code above prints random UInt32
number. You can safely initialise Int
with it if you want.
Post scriptum
Swift 4.2 is going to introduce a few really cool functions, which make access to old C-style API much easier. It also adds shuffling and randomising for collections. If you want to learn more about this, please take a look at SE-0202 proposal.
Still hungry for knowledge?
If yes, then I'm pretty sure this short blog post doesn't reply to all your questions. Feel free to ask me anything, but before let me recommend you two great sources of knowledge for randomising numbers.
- GameplayKit documentation, especially for GKRandomSource
- Security framework's Randomization Services documentation
- Really AMAZING speech on try! Swift by Natalia Berdys