Swift: Random number for 64-bit integers?
Asked Answered
H

5

15

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.

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.

Any advice?

Hainan answered 24/10, 2014 at 14:20 Comment(0)
P
27

Update: As of Swift 4.2 (distributed with Xcode 10.1) there is a unified random API in the Swift standard library, see

You can simply call

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 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://mcmap.net/q/15753/-why-do-people-say-there-is-modulo-bias-when-using-a-random-number-generator from above thread to 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.)

Putumayo answered 24/10, 2014 at 14:37 Comment(2)
Is this solution cryptographically secure?Mota
@AnkitJayaswal: According to developer.apple.com/documentation/swift/…, UInt64.random(in:) is cryptographically secure (and uses arc4random_buf under the hood on Apple platforms).Putumayo
M
17

Perhaps you can compose it of two 32 bit integers:

var random64 = Int64(arc4random()) + (Int64(arc4random()) << 32)
Mennonite answered 24/10, 2014 at 14:35 Comment(2)
Sasha Lopoukhine is using &+, as in Int64(arc4random()) &+ (Int64(arc4random()) << 32). Is + better than &+?Rootlet
According to Sasha, &+ is better here. :)Rootlet
M
2

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().

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)))
    }
}
Madelyn answered 7/10, 2016 at 14:10 Comment(2)
Kirsteins is using +, as in (randomLowerHalf() << 32) + randomLowerHalf(). Is &+ better than +?Rootlet
The &+ means that there is no overflow check. This is safe to use in this particular instance because you know that an overflow won't happen.Madelyn
B
2

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

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)!
    }
}
Boccherini answered 25/3, 2017 at 18:43 Comment(0)
G
1

You can use UInt64.random(in:) and UInt64.max APIs to generate a random unsigned 64-bit number:

UInt64.random(in: 0...UInt64.max)
Gullible answered 22/1, 2022 at 18:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.