Swift: случайное число для 64-битных целых чисел?

Итак, с моим текущим проектом мне нужно работать с 64-битными целыми числами, и мне нужно захватить случайные числа между диапазонами до 100 миллиардов. arc4random()/arc4random_uniform() работает только с 32-битными целыми знаками без знака.

Возможно, я немного помассирую его, потому что мой диапазон min/max для каждого звонка, вероятно, не превысит 2 миллиарда, но я бы хотел, чтобы будущая защита была на тот случай, если я решила, что мне нужен более широкий диапазон.

Любые советы?

Ответ 1

Обновление: Начиная с Swift 4.2 (распространяется с Xcode 10.1) в стандартной библиотеке Swift есть унифицированный случайный API, см.

С недавним снимком разработчика Swift 4.2 вы можете просто позвонить

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 (см. Почему люди говорят, что при использовании генератора случайных чисел существует смещение по модулю?). Здесь я перевел ответ fooobar.com/questions/7093/... сверху ветки на 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 итераций.)

Ответ 2

Возможно, вы можете записать его в 32-битные целые числа:

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

Ответ 3

Вот некоторые помощники, которые я обычно включаю в свои проекты. Обратите внимание на ограниченный помощник 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

Вот одно опрятное решение! (все равно, так как я только что закончил)

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

В качестве расширения...

import Foundation


extension UInt64 {

    static var random: UInt64 {

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

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