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

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

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

Любой совет?


person RH224    schedule 24.10.2014    source источник


Ответы (4)


Обновление: начиная с 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://stackoverflow.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
}

(На первый взгляд кажется, что цикл может и не завершиться, но можно показать, что в среднем требуется менее 2 итераций.)

person Martin R    schedule 24.10.2014
comment
Является ли это решение криптографически безопасным? - person Ankit Jayaswal; 12.04.2019
comment
@AnkitJayaswal: Согласно developer.apple.com/documentation/swift/, UInt64.random(in:) является криптографически безопасный (и использует arc4random_buf под капотом на платформах Apple). - person Martin R; 12.04.2019

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

var random64 = Int64(arc4random()) + (Int64(arc4random()) << 32)
person Kirsteins    schedule 24.10.2014
comment
Саша Лопухин использует &+, как в Int64(arc4random()) &+ (Int64(arc4random()) << 32). + лучше, чем &+? - person Cœur; 27.09.2017
comment
По словам Саши, здесь &+ лучше. :) - person Cœur; 28.09.2017

Вот несколько помощников, которых я обычно включаю в свои проекты. Обратите внимание на ограниченный помощник UInt64, он работает в основном так же, как ответ Мартина Р, за исключением проверок для частого случая, когда диапазон меньше, чем 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)))
    }
}
person Sasha Lopoukhine    schedule 07.10.2016
comment
Кирстейнс использует +, как и (randomLowerHalf() << 32) + randomLowerHalf(). &+ лучше, чем +? - person Cœur; 27.09.2017
comment
&+ означает, что проверка на переполнение отсутствует. Это безопасно использовать в данном конкретном случае, потому что вы знаете, что переполнения не произойдет. - person Sasha Lopoukhine; 28.09.2017

Вот одно изящное решение! (думаю, во всяком случае, так как я только что сделал это)

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)!
    }
}
person Mazyod    schedule 25.03.2017