我正在寻找一种有效的方法来在Swift中生成具有任意范围(甚至可能是UInt.max或Int.max)的大数(包括浮点类型!)。
UInt.max
Int.max
我见过的所有现有问题要么因大值(UInt.max)而崩溃,要么不支持范围。我知道您可以从中读取/dev/urandom随机字节,但这无助于将这些值限制为给定的时间间隔(而且我敢肯定,循环直到无效为止)。
/dev/urandom
这是的可能解决方案UInt,Int并且Double可以与所有这些类型一起使用。它被编写为扩展方法(现已针对Swift2进行了更新),但是对于全局函数也可以做到这一点。
UInt
Int
Double
请注意,仅arc4random_uniform()产生32位数字,因此如果Int/ UInt是64位整数(所有OS X机器和所有较新的iOS设备都是这种情况),则不能使用该数字。
arc4random_uniform()
因为UInt我们使用的技术(这只是的快速翻译)。如果范围涵盖的整个范围,则将UInt单独处理。
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 } } }
例子:
let u1 = UInt.random(minValue: 1000, maxValue: 2000) let u2 = UInt.random(minValue: UInt.min, maxValue: UInt.max)
使用溢出运算符和bitPattern:转换可以将有符号整数的情况减少为无符号情况:
bitPattern:
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) } }
let i1 = Int.random(minValue: -1000, maxValue: 1000) let i2 = Int.random(minValue: Int.min, maxValue: Int.max)
最后,一个简单的实现Double:
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) } }
例:
let d = Double.random(minValue: 10.5, maxValue: 123.5)
Swift 3更新:
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) } }