如何在Swift中生成大范围的随机数?

How can I generate large, ranged random numbers in Swift?

我正在寻找一种有效的方法来在Swift中生成具有任意范围(甚至可能是UInt.maxInt.max)的大数(包括浮点类型!)。

我已经看到的所有现有问题要么因大值(UInt.max)而崩溃,要么不支持范围。 我知道您可以从/dev/urandom中读取随机字节,但这无助于将这些值限制为给定的时间间隔(而且我敢肯定,循环直到无效为止)。


这是UIntIntDouble的可能解决方案,可与
这些类型的全部范围。它被写为扩展方法
(现已针对Swift 2进行了更新),但是对于全局函数也可以做到这一点。

请注意,arc4random_uniform()仅产生32位数字,因此
如果Int / UInt是64位整数(这种情况下),则不能使用
适用于所有OS X计算机和所有较新的iOS设备)。

对于UInt,我们使用https://stackoverflow.com/a/26550169/1187415的技术
(这只是https://stackoverflow.com/a/10989061/1187415的Swift翻译)。
范围覆盖整个UInt的情况将单独处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
extension UInt {
    static func random(minValue minValue : UInt, maxValue : UInt) -> UInt {
        precondition(minValue <= maxValue,"attempt to call random() with minValue > maxValue")

        if minValue == UInt.min && maxValue == UInt.max {
            // Random number in the full range of UInt:

            var rnd : UInt = 0
            arc4random_buf(&rnd, sizeofValue(rnd))
            return rnd
        } else {
            // Compute random number in the range 0 ... (maxValue-minValue),
            // using the technique from
            // https://stackoverflow.com/a/26550169/1187415, https://stackoverflow.com/a/10989061/1187415
            // and avoiding the"modulo bias problem":

            let range = maxValue - minValue + 1
            let randLimit = UInt.max - UInt.max % range
            var rnd : UInt = 0
            repeat {
                arc4random_buf(&rnd, sizeofValue(rnd))
            } while rnd >= randLimit
            rnd = rnd % range

            // Transform `rnd` back to the range minValue ... maxValue:
            return minValue + rnd
        }
    }
}

例子:

1
2
let u1 = UInt.random(minValue: 1000, maxValue: 2000)
let u2 = UInt.random(minValue: UInt.min, maxValue: UInt.max)

使用可以将有符号整数的情况减少为无符号情况。
溢出运算符和bitPattern:转换:

1
2
3
4
5
6
7
8
9
10
11
12
extension Int {
    static func random(minValue minValue : Int, maxValue : Int) -> Int {
        precondition(minValue <= maxValue,"attempt to call random() with minValue > maxValue")

        // Compute unsigned random number in the range 0 ... (maxValue-minValue):
        let diff = UInt(bitPattern: maxValue &- minValue)
        let rnd = UInt.random(minValue: 0, maxValue: diff)

        // Transform `rnd` back to the range minValue ... maxValue:
        return minValue &+ Int(bitPattern: rnd)
    }
}

例子:

1
2
let i1 = Int.random(minValue: -1000, maxValue: 1000)
let i2 = Int.random(minValue: Int.min, maxValue: Int.max)

最后,Double的直接实现:

1
2
3
4
5
6
7
8
9
10
11
extension Double {
    static func random(minValue minValue : Double, maxValue : Double) -> Double {
        precondition(minValue <= maxValue,"attempt to call random() with minValue > maxValue")

        // Random floating point number in the range 0.0 ... 1.0:
        let rnd = Double(UInt.random(minValue: 0, maxValue: UInt.max))/Double(UInt.max)

        // Scale to range minValue ... maxValue:
        return minValue + rnd * (maxValue - minValue)
    }
}

例:

1
let d = Double.random(minValue: 10.5, maxValue: 123.5)

Swift 3更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
extension UInt {
    static func random(minValue: UInt, maxValue: UInt) -> UInt {
        precondition(minValue <= maxValue,"attempt to call random() with minValue > maxValue")

        if minValue == UInt.min && maxValue == UInt.max {
            // Random number in the full range of UInt:

            var rnd: UInt = 0
            arc4random_buf(&rnd, MemoryLayout.size(ofValue: rnd))
            return rnd
        } else {
            // Compute random number in the range 0 ... (maxValue-minValue),
            // using the technique from
            // https://stackoverflow.com/a/26550169/1187415, https://stackoverflow.com/a/10989061/1187415
            // and avoiding the"modulo bias problem":

            let range = maxValue - minValue + 1
            let randLimit = UInt.max - UInt.max % range
            var rnd: UInt = 0
            repeat {
                arc4random_buf(&rnd, MemoryLayout.size(ofValue: rnd))
            } while rnd >= randLimit
            rnd = rnd % range

            // Transform `rnd` back to the range minValue ... maxValue:
            return minValue + rnd
        }
    }
}

extension Int {
    static func random(minValue: Int, maxValue: Int) -> Int {
        precondition(minValue <= maxValue,"attempt to call random() with minValue > maxValue")

        // Compute unsigned random number in the range 0 ... (maxValue-minValue):
        let diff = UInt(bitPattern: maxValue &- minValue)
        let rnd = UInt.random(minValue: 0, maxValue: diff)

        // Transform `rnd` back to the range minValue ... maxValue:
        return minValue &+ Int(bitPattern: rnd)
    }
}

extension Double {
    static func random(minValue: Double, maxValue: Double) -> Double {
        precondition(minValue <= maxValue,"attempt to call random() with minValue > maxValue")

        // Random floating point number in the range 0.0 ... 1.0:
        let rnd = Double(UInt.random(minValue: 0, maxValue: UInt.max))/Double(UInt.max)

        // Scale to range minValue ... maxValue:
        return minValue + rnd * (maxValue - minValue)
    }
}