Swift does not provide a feature to directly create an array of random numbers. Such an array must be built by generating a random number and assigning that number to an array element. The following sections demonstrate several methods to generate random numbers in Swift. Each method is benchmarked by filling a large array with random numbers.
Swift provides a static random method for different types such as Double.random
. This uses the system's default random number generator which is cryptographically secure whenever possible. The code below uses Double.random
to fill an array of 100,000,000 elements with random numbers that range from 0 to 1 (excluding 1).
// rand.swift
func main() {
let n = 100_000_000
let result = Array<Double>(unsafeUninitializedCapacity: n) { buffer, initCount in
for i in 0..<n {
buffer[i] = Double.random(in: 0..<1)
}
initCount = n
}
print(result[0])
print(result[n - 1])
}
main()
The Accelerate framework provides a drand48
function that returns a random double-precision value over a uniform distribution [0, 1). This function generates pseudo-random numbers using a linear congruential algorithm with 48-bit integer arithmetic. The code below is similar to the previous example except drand48
generates the random numbers for the array elements.
// rand48.swift
import Accelerate
func main() {
let n = 100_000_000
let result = Array<Double>(unsafeUninitializedCapacity: n) { buffer, initCount in
for i in 0..<n {
buffer[i] = drand48()
}
initCount = n
}
print(result[0])
print(result[n - 1])
}
main()
The Accelerate framework also provides access to LAPACK routines. The LAPACK dlarnv function creates an array of random numbers from a 0 to 1 uniform distribution when idist
is 1.
// lapack.swift
import Accelerate
func main() {
let n = 100_000_000
let result = Array<Double>(unsafeUninitializedCapacity: n) { buffer, initCount in
var idist: Int32 = 1
var nn = Int32(n)
var iseed: [Int32] = (0..<3).map { _ in Int32.random(in: 1..<4095) }
iseed += [2 * (Int32.random(in: 1..<4095) / 2) + 1 ]
dlarnv_(&idist, &iseed, &nn, buffer.baseAddress)
initCount = n
}
print(result[0])
print(result[n - 1])
}
main()
The static random methods like Double.random
accept a RandomNumberGenerator. The example below is a Swift implementation of the wyrand pseudo-random number generator which is associated with the wyhash function by Wang Yi. The generator is passed to the Double.random
method to create the random number for the array element. There is also the SwiftWyhash port by Daniel Lemire that is based on an older version of wyrand. Also see Daniel's 2019 article about fast random number generators.
// wyrand.swift
import Foundation
struct WyRand: RandomNumberGenerator {
private var state : UInt64
init(seed: UInt64 = mach_absolute_time()) {
state = seed
}
mutating func next() -> UInt64 {
state &+= 0x2d358dccaa6c78a5
let mul = state.multipliedFullWidth(by: state ^ 0x8bb84b93962eacc9)
return mul.low ^ mul.high
}
}
func main() {
let n = 100_000_000
var rng = WyRand()
let result = Array<Double>(unsafeUninitializedCapacity: n) { buffer, initCount in
for i in 0..<n {
buffer[i] = Double.random(in: 0..<1, using: &rng)
}
initCount = n
}
print(result[0])
print(result[n - 1])
}
main()
This example is similar to the WyRand code given above except Double.random
is not used. The integer returned from the generator is converted to a double by dividing by UInt64.max
+ 1 which is 18446744073709551616
. The result is a random number in the range [0, 1) including 0 but excluding 1.
// wyuniform.swift
import Foundation
struct WyRand: RandomNumberGenerator {
private var state : UInt64
init(seed: UInt64 = mach_absolute_time()) {
state = seed
}
mutating func next() -> UInt64 {
state &+= 0x2d358dccaa6c78a5
let mul = state.multipliedFullWidth(by: state ^ 0x8bb84b93962eacc9)
return mul.low ^ mul.high
}
}
func main() {
let n = 100_000_000
var rng = WyRand()
let result = Array<Double>(unsafeUninitializedCapacity: n) { buffer, initCount in
for i in 0..<n {
let r = rng.next()
let uniform = Double(r) / 18446744073709551616
buffer[i] = uniform
}
initCount = n
}
print(result[0])
print(result[n - 1])
}
main()
The compiler and benchmark commands for each example are shown below. The hyperfine command-line tool is used to benchmark the compiled code.
# Compile the code
swiftc -Xcc -DACCELERATE_NEW_LAPACK -Ounchecked lapack.swift
swiftc -Ounchecked rand.swift
swiftc -Ounchecked rand48.swift
swiftc -Ounchecked wyrand.swift
swiftc -Ounchecked wyuniform.swift
# Run then benchmark
hyperfine --warmup 3 './lapack' './rand' './rand48' './wyrand' './wyuniform'
A summary of the benchmark results is shown in the table below. The Time column is the mean time in milliseconds. The Speedup column is how much faster WyRand uniform is compared to the other methods. The results were obtained on a 2025 M4 MacBook Air with 32 GB of unified memory and with Swift version 6.1.2. The WyRand uniform generator was 50x faster than the default random number generator in Swift when filling an array with 100,000,000 random numbers.
Method | Filename | Time (ms) | Speedup |
---|---|---|---|
Swift random | rand.swift | 3318 | 50.5x |
LAPACK dlarnv | lapack.swift | 407 | 6.2x |
Accelerate drand48 | rand48.swift | 185 | 2.8x |
WyRand random | wyrand.swift | 81 | 1.2x |
WyRand uniform | wyuniform.swift | 65 | - |
Based on the benchmarks, using Swift's default random number generator is very slow compared to pseudo-random number generators such as wyrand. If you don't need cryptographically secure random numbers then consider using a custom random number generator for faster performance; especially when filling arrays with random numbers.
Gavin Wiggins © 2025.
Made on a Mac with Genja. Hosted on GitHub Pages.