Swift:64位整数的随机数?

时间:2022-02-12 12:02:47

So, with my current project, I need to work with 64-bit integers and I need to grab random numbers between ranges up to 100 billion. arc4random()/arc4random_uniform() only works with unsigned 32-bit integers.

因此,在我目前的项目中,我需要使用64位整数,我需要在范围高达1000亿之间获取随机数。 arc4random()/ arc4random_uniform()仅适用于无符号32位整数。

I can probably fudge it a little because my min/max range for every call will likely not exceed 2 billion, but I'd like to futureproof myself in case I decide that, well, I do need a broader range.

我可能会稍微捏一下,因为我的每次通话的最小/最大范围可能不会超过20亿,但我想在未来的情况下保护自己以防万一我确实需要更广泛的范围。

Any advice?

任何建议?

4 个解决方案

#1


22  

Update: As of Swift 4.2 (currently in beta) there is a unified random API in the Swift standard library, see

更新:从Swift 4.2(目前处于测试版)开始,Swift标准库中有一个统一的随机API,请参阅

With a recent Swift 4.2 developer snapshot you can simply call

使用最近的Swift 4.2开发人员快照,您只需拨打电话即可

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

to get a random number in the given range.

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


(Previous answer for Swift < 4.2:) With arc4random_buf() you can create "arbitrary large" random numbers, so this would be a possible solution:

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

This method suffers from the "modulo bias" problem when the upper bound is not a power of 2 (See Why do people say there is modulo bias when using a random number generator?). Here I have translated the answer https://*.com/a/10989061/1187415 from above thread to Swift:

当上限不是2的幂时,这种方法会遇到“模偏”问题(参见为什么人们说使用随机数发生器时存在模偏差?)。在这里,我将答案https://*.com/a/10989061/1187415从上面的帖子转换为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
}

(At first sight it looks as if the loop might not terminate, but it can be shown that on average less than 2 iterations are needed.)

(乍一看,它看起来好像循环可能不会终止,但可以证明平均需要少于2次迭代。)

#2


17  

Perhaps you can compose it of to 32 bit integers:

也许你可以把它组成32位整数:

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

#3


1  

Here are some helpers that I usually include in my projects. Note the UInt64 bounded helper, it works largely in the same way to Martin R's answer, except for checks for the frequent case that the range is smaller than UInt32.max and only performs one call to arc4random().

以下是我通常在项目中包含的一些助手。注意UInt64有界帮助器,它在很大程度上与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)))
    }
}

#4


1  

Here is one neat solution! (methinks anyway, since I just made it up)

这是一个简洁的解决方案! (无论如何,因为我刚刚完成了)

let hex = UUID().uuidString.components(separatedBy: "-").suffix(2).joined()
let rand = UInt64(hex, radix: 0x10)

Quick test with Swift REPL:

https://repl.it/GeIs/0

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

#1


22  

Update: As of Swift 4.2 (currently in beta) there is a unified random API in the Swift standard library, see

更新:从Swift 4.2(目前处于测试版)开始,Swift标准库中有一个统一的随机API,请参阅

With a recent Swift 4.2 developer snapshot you can simply call

使用最近的Swift 4.2开发人员快照,您只需拨打电话即可

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

to get a random number in the given range.

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


(Previous answer for Swift < 4.2:) With arc4random_buf() you can create "arbitrary large" random numbers, so this would be a possible solution:

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

This method suffers from the "modulo bias" problem when the upper bound is not a power of 2 (See Why do people say there is modulo bias when using a random number generator?). Here I have translated the answer https://*.com/a/10989061/1187415 from above thread to Swift:

当上限不是2的幂时,这种方法会遇到“模偏”问题(参见为什么人们说使用随机数发生器时存在模偏差?)。在这里,我将答案https://*.com/a/10989061/1187415从上面的帖子转换为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
}

(At first sight it looks as if the loop might not terminate, but it can be shown that on average less than 2 iterations are needed.)

(乍一看,它看起来好像循环可能不会终止,但可以证明平均需要少于2次迭代。)

#2


17  

Perhaps you can compose it of to 32 bit integers:

也许你可以把它组成32位整数:

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

#3


1  

Here are some helpers that I usually include in my projects. Note the UInt64 bounded helper, it works largely in the same way to Martin R's answer, except for checks for the frequent case that the range is smaller than UInt32.max and only performs one call to arc4random().

以下是我通常在项目中包含的一些助手。注意UInt64有界帮助器,它在很大程度上与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)))
    }
}

#4


1  

Here is one neat solution! (methinks anyway, since I just made it up)

这是一个简洁的解决方案! (无论如何,因为我刚刚完成了)

let hex = UUID().uuidString.components(separatedBy: "-").suffix(2).joined()
let rand = UInt64(hex, radix: 0x10)

Quick test with Swift REPL:

https://repl.it/GeIs/0

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