Skip to content

Instantly share code, notes, and snippets.

@jstn
Last active May 5, 2023 03:26
Show Gist options
  • Star 30 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save jstn/f9d5437316879c9c448a to your computer and use it in GitHub Desktop.
Save jstn/f9d5437316879c9c448a to your computer and use it in GitHub Desktop.
generate random numbers for 64-bit types while mitigating modulo bias
/*
`arc4random_uniform` is very useful but limited to `UInt32`.
This defines a generic version of `arc4random` for any type
expressible by an integer literal, and extends some numeric
types with a `random` method that mitigates for modulo bias
in the same manner as `arc4random`.
`lower` is inclusive and `upper` is exclusive, thus:
let diceRoll = UInt64.random(lower: 1, upper: 7)
*/
import Darwin
private let _wordSize = __WORDSIZE
public func arc4random<T: ExpressibleByIntegerLiteral>(_ type: T.Type) -> T {
var r: T = 0
arc4random_buf(&r, MemoryLayout<T>.size)
return r
}
public extension UInt {
public static func random(lower: UInt = min, upper: UInt = max) -> UInt {
switch (_wordSize) {
case 32: return UInt(UInt32.random(lower: UInt32(lower), upper: UInt32(upper)))
case 64: return UInt(UInt64.random(lower: UInt64(lower), upper: UInt64(upper)))
default: return lower
}
}
}
public extension Int {
public static func random(lower: Int = min, upper: Int = max) -> Int {
switch (_wordSize) {
case 32: return Int(Int32.random(lower: Int32(lower), upper: Int32(upper)))
case 64: return Int(Int64.random(lower: Int64(lower), upper: Int64(upper)))
default: return lower
}
}
}
public extension UInt32 {
public static func random(lower: UInt32 = min, upper: UInt32 = max) -> UInt32 {
return arc4random_uniform(upper - lower) + lower
}
}
public extension Int32 {
public static func random(lower: Int32 = min, upper: Int32 = max) -> Int32 {
let r = arc4random_uniform(UInt32(Int64(upper) - Int64(lower)))
return Int32(Int64(r) + Int64(lower))
}
}
public extension UInt64 {
public static func random(lower: UInt64 = min, upper: UInt64 = max) -> UInt64 {
var m: UInt64
let u = upper - lower
var r = arc4random(UInt64.self)
if u > UInt64(Int64.max) {
m = 1 + ~u
} else {
m = ((max - (u * 2)) + 1) % u
}
while r < m {
r = arc4random(UInt64.self)
}
return (r % u) + lower
}
}
public extension Int64 {
public static func random(lower: Int64 = min, upper: Int64 = max) -> Int64 {
let (s, overflow) = Int64.subtractWithOverflow(upper, lower)
let u = overflow ? UInt64.max - UInt64(~s) : UInt64(s)
let r = UInt64.random(upper: u)
if r > UInt64(Int64.max) {
return Int64(r - (UInt64(~lower) + 1))
} else {
return Int64(r) + lower
}
}
}
@pontusarmini
Copy link

This is great, thanks! One thing that has changed since this was uploaded is that arc4random_buf no longer takes a UInt as an argument, but a instead a regular Int. So the arc4random function should be changed to this:

public func arc4random <T: IntegerLiteralConvertible> (type: T.Type) -> T {
    var r:T = 0
    arc4random_buf(&r, Int(sizeof(T)))
    return r
}

@zmeyc
Copy link

zmeyc commented Sep 5, 2015

To suppress 4 warnings in Swift 2.0 a global variable can be used:

let wordSize = __WORDSIZE
extension ... {
  switch wordSize {

@jstn
Copy link
Author

jstn commented Sep 6, 2016

@zmeyc @pontusarmini thank you both! this is now updated for swift 3 and MemoryLayout

@Hsanka6
Copy link

Hsanka6 commented Mar 27, 2017

this has been giving me duplicates when I try to pick 5 ints from an array of size between 10 to 30

@DivineDominion
Copy link

A Swift 4.1 compliant variant that infers the type from the return type, which is often closer aligned to the Swift's style guideline stating you should prefer let foo: Int = 0 over let foo = Int(0):

func arc4random<T: ExpressibleByIntegerLiteral>() -> T {
    var r: T = 0
    arc4random_buf(&r, MemoryLayout<T>.size)
    return r
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment