How does one generate a random number in Apple's Swift language?

后端 未结 25 2123
有刺的猬
有刺的猬 2020-11-22 09:51

I realize the Swift book provided an implementation of a random number generator. Is the best practice to copy and paste this implementation in one\'s own program? Or is t

25条回答
  •  北荒
    北荒 (楼主)
    2020-11-22 10:15

    Edit: Updated for Swift 3.0

    arc4random works well in Swift, but the base functions are limited to 32-bit integer types (Int is 64-bit on iPhone 5S and modern Macs). Here's a generic function for a random number of a type expressible by an integer literal:

    public func arc4random(_ type: T.Type) -> T {
        var r: T = 0
        arc4random_buf(&r, MemoryLayout.size)
        return r
    }
    

    We can use this new generic function to extend UInt64, adding boundary arguments and mitigating modulo bias. (This is lifted straight from arc4random.c)

    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
        }
    }
    

    With that we can extend Int64 for the same arguments, dealing with overflow:

    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 complete the family...

    private let _wordSize = __WORDSIZE
    
    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 UInt {
        public static func random(lower: UInt = min, upper: UInt = max) -> UInt {
            switch (_wordSize) {
                case 32: return UInt(UInt32.random(UInt32(lower), upper: UInt32(upper)))
                case 64: return UInt(UInt64.random(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(Int32(lower), upper: Int32(upper)))
                case 64: return Int(Int64.random(Int64(lower), upper: Int64(upper)))
                default: return lower
            }
        }
    }
    

    After all that, we can finally do something like this:

    let diceRoll = UInt64.random(lower: 1, upper: 7)
    

提交回复
热议问题