Last active
May 5, 2023 03:26
-
-
Save jstn/f9d5437316879c9c448a to your computer and use it in GitHub Desktop.
generate random numbers for 64-bit types while mitigating modulo bias
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
`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 | |
} | |
} | |
} |
To suppress 4 warnings in Swift 2.0 a global variable can be used:
let wordSize = __WORDSIZE
extension ... {
switch wordSize {
@zmeyc @pontusarmini thank you both! this is now updated for swift 3 and MemoryLayout
this has been giving me duplicates when I try to pick 5 ints from an array of size between 10 to 30
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
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: