Swift:生成64位整数的随机数?

14

因为我的当前项目需要使用64位整数,并且我需要在高达1000亿的范围内获取随机数字,而arc4random()/arc4random_uniform()仅适用于无符号32位整数。

可能我可以做一些小调整,因为每次调用我的最小/最大范围可能不会超过20亿,但是我想要未雨绸缪以防我决定需要更广泛的范围。

有什么建议吗?

5个回答

26

更新:Swift 4.2 (随 Xcode 10.1 分发) 起,Swift 标准库中有一个统一的随机 API,请参见

您可以直接调用它。

UInt64.random(in: minValue ... maxValue)

在给定的范围内获取一个随机数。


(Swift 4.2 以前版本的答案:) 使用 arc4random_buf(),您可以创建“任意大”的随机数,因此这是可能的解决方案:

// Swift 2:
func random64(upper_bound: UInt64) -> UInt64 {

    // Generate 64-bit random number:
    var rnd : UInt64 = 0
    arc4random_buf(&rnd, sizeofValue(rnd))

    return rnd % upper_bound
}

// Swift 3:
func random64(upper_bound: UInt64) -> UInt64 {

    // Generate 64-bit random number:
    var rnd : UInt64 = 0
    arc4random_buf(&rnd, MemoryLayout.size(ofValue: rnd))

    return rnd % upper_bound
}

当上限不是2的次幂时,该方法存在“模数偏差”问题(参见为什么有人说使用随机数生成器时会出现模数偏差?)。这里我已将上述线程中的答案https://dev59.com/12gu5IYBdhLWcg3w6bKo#10989061翻译成Swift:

// Swift 2:
func random64(upper_bound: UInt64) -> UInt64 {

    // Generate 64-bit random value in a range that is
    // divisible by upper_bound:
    let range = UInt64.max - UInt64.max % upper_bound
    var rnd : UInt64 = 0
    repeat {
        arc4random_buf(&rnd, sizeofValue(rnd))
    } while rnd >= range

    return rnd % upper_bound
}

// Swift 3:
func random64(upper_bound: UInt64) -> UInt64 {

    // Generate 64-bit random value in a range that is
    // divisible by upper_bound:
    let range = UInt64.max - UInt64.max % upper_bound
    var rnd : UInt64 = 0
    repeat {
        arc4random_buf(&rnd, MemoryLayout.size(ofValue: rnd))
    } while rnd >= range

    return rnd % upper_bound
}

乍一看,循环似乎可能不会终止,但可以证明平均不到2次迭代就足够了。


这个解决方案是否具有加密安全性? - Ankit Jayaswal
根据 https://developer.apple.com/documentation/swift/systemrandomnumbergenerator,`UInt64.random(in:)` 是具有密码学安全性的(在 Apple 平台上使用 arc4random_buf)。 - Martin R

17

也许你可以由两个32位整数组成它:

var random64 = Int64(arc4random()) + (Int64(arc4random()) << 32)

Sasha Lopoukhine正在使用&+,例如Int64(arc4random()) &+ (Int64(arc4random()) << 32)+&+更好吗? - Cœur
根据Sasha的说法,&+在这里更好。 :) - Cœur

2

以下是我通常在项目中包含的一些辅助函数。请注意 UInt64 bounded helper 函数,它与 Martin R 的答案大致相同,不同之处在于该函数检查范围是否小于 UInt32.max,并且仅执行一次 arc4random() 调用。

extension UInt32 {

    /// Returns a random number in `0...UInt32.max`
    static func random() -> UInt32 {
        return arc4random()
    }

    /// Returns a random number in `0..<upperBound`
    static func random(_ upperBound: UInt32) -> UInt32 {
        return arc4random_uniform(upperBound)
    }
}

extension UInt64 {

    private static func randomLowerHalf() -> UInt64 {
        return UInt64(UInt32.random())
    }

    private static func randomLowerHalf(_ upperBound: UInt32) -> UInt64 {
        return UInt64(UInt32.random(upperBound))
    }

    static func random() -> UInt64 {
        return (randomLowerHalf() << 32) &+ randomLowerHalf()
    }

    static func random(_ upperBound: UInt64) -> UInt64 {
        if let upperBound = UInt32(exactly: upperBound) {
            return randomLowerHalf(upperBound)
        } else if UInt64(UInt32.max) == upperBound {
            return randomLowerHalf()
        } else {
            var result: UInt64
            repeat {
                result = random()
            } while result >= upperBound
            return result
        }
    }
}

extension Int32 {

    static func random() -> Int32 {
        return Int32(bitPattern: UInt32.random())
    }

    static func random(_ upperBound: Int32) -> Int32 {
        assert(0 < upperBound, "upperBound(\(upperBound)) must be greater than 0")
        return Int32(bitPattern: UInt32.random(UInt32(bitPattern: upperBound)))
    }
}

extension Int64 {

    static func random() -> Int64 {
        return Int64(bitPattern: UInt64.random())
    }

    static func random(_ upperBound: Int64) -> Int64 {
        assert(0 < upperBound, "upperBound(\(upperBound)) must be greater than 0")
        return Int64(bitPattern: UInt64.random(UInt64(bitPattern: upperBound)))
    }
}

extension Int {

    static func random() -> Int {
        return Int(IntMax.random())
    }

    static func random(_ upperBound: Int) -> Int {
        assert(0 < upperBound, "upperBound(\(upperBound)) must be greater than 0")
        return Int(IntMax.random(IntMax(upperBound)))
    }
}

Kirsteins正在使用+,例如(randomLowerHalf() << 32) + randomLowerHalf()&++更好吗? - Cœur
1
&+ 的意思是没有溢出检查。在这种特定情况下使用是安全的,因为您知道不会发生溢出。 - Sasha Lopoukhine

2
这里有一个不错的解决方案!(我认为是这样,因为我刚想出来)
let hex = UUID().uuidString.components(separatedBy: "-").suffix(2).joined()
let rand = UInt64(hex, radix: 0x10)

使用Swift REPL进行快速测试:

https://repl.it/GeIs/0

for _ in 0..<5_000_000 {
    let hex = UUID().uuidString.components(separatedBy: "-").suffix(2).joined()
    set.insert(UInt64(hex, radix: 0x10)!)
}
set.count // prints 5_000_000

As an extension...

import Foundation


extension UInt64 {

    static var random: UInt64 {

        let hex = UUID().uuidString
            .components(separatedBy: "-")
            .suffix(2)
            .joined()

        return UInt64(hex, radix: 0x10)!
    }
}

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接