14

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?

RH224
  • 141
  • 1
  • 5

5 Answers5

26

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://stackoverflow.com/a/10989061/1187415 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.)

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Is this solution cryptographically secure? – Ankit Jayaswal Apr 12 '19 at 11:16
  • @AnkitJayaswal: According to https://developer.apple.com/documentation/swift/systemrandomnumbergenerator, `UInt64.random(in:)` is cryptographically secure (and uses arc4random_buf under the hood on Apple platforms). – Martin R Apr 12 '19 at 11:24
17

Perhaps you can compose it of two 32 bit integers:

var random64 = Int64(arc4random()) + (Int64(arc4random()) << 32)
Cœur
  • 37,241
  • 25
  • 195
  • 267
Kirsteins
  • 27,065
  • 8
  • 76
  • 78
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)))
    }
}
  • Kirsteins is using `+`, as in `(randomLowerHalf() << 32) + randomLowerHalf()`. Is `&+` better than `+`? – Cœur Sep 27 '17 at 10:01
  • 1
    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. – Sasha Lopoukhine Sep 28 '17 at 10:57
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)!
    }
}
Mazyod
  • 22,319
  • 10
  • 92
  • 157
0

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

UInt64.random(in: 0...UInt64.max)
Madiyar
  • 779
  • 11
  • 7