views:

887

answers:

6

I need to generate arbitrarily large random integers in the range 0 (inclusive) to n (exclusive). My initial thought was to call nextDouble and multiply by n, but once n gets to be larger than 253, the results would no longer be uniformly distributed.

BigInteger has the following constructor available:

public BigInteger(int numBits, Random rnd)

Constructs a randomly generated BigInteger, uniformly distributed over the range 0 to (2numBits - 1), inclusive.

How can this be used to get a random value in the range 0 - n, where n is not a power of 2?

+7  A: 

The simplest approach (by quite a long way) would be to use the specified constructor to generate a random number with the right number of bits (ceil(log2 n)), and then throw it away if it's greater than n. In the worst possible case (e.g. a number in the range [0, 2n + 1) you'll throw away just under half the values you create, on average.

Jon Skeet
Wow, I had the code ready to copy/paste and you still beat me to the answer by a comfortable margin.
Bill the Lizard
+7  A: 

Use a loop:

BigInteger r;
do {
    r = new BigInteger(n.bitLength(), rnd);
} while (r.compareTo(n) >= 0);

on average, this will require less than two iterations, and the selection will be uniform.

Edit: If your RNG is expensive, you can limit the number of iterations the following way:

int nlen = n.bitLength();
BigInteger nm1 = n.subtract(BigInteger.ONE);
BigInteger r, s;
do {
    s = new BigInteger(nlen + 100, rnd);
    r = s.mod(n);
} while (s.subtract(r).add(nm1).bitLength() >= nlen + 100);
// result is in 'r'

With this version, it is highly improbable that the loop is taken more than once (less than one chance in 2^100, i.e. much less than the probability that the host machine spontaneously catches fire in the next following second). On the other hand, the mod() operation is computationally expensive, so this version is probably slower than the previous, unless the rnd instance if exceptionally slow.

Thomas Pornin
+4  A: 

The following method uses the BigInteger(int numBits, Random rnd) constructor and rejects the result if it's bigger than the specified n.

public BigInteger nextRandomBigInteger(BigInteger n) {
    Random rand = new Random();
    BigInteger result = new BigInteger(n.bitLength(), rand);
    while( result.compareTo(n) >= 0 ) {
        result = new BigInteger(n.bitLength(), rand);
    }
    return result;
}

The drawback to this is that the constructor is called an unspecified number of times, but in the worst case (n is just slightly greater than a power of 2) the expected number of calls to the constructor should be only about 2 times.

Bill the Lizard
In the worst case, average number of calls should be around 2, not 1.5: 1 call (always), +1 (0.5 prob.), +1 (0.5*0.5 prob.), +1 (0.5*0.5*0.5 prob.)... this converges on 2, not 1.5. Not that it makes that huge a difference. A more visual description is: there is only one in a million chance that it performs more than twenty random number generations.
Thomas Pornin
@Thomas Pornin: I came up with 1.5 because in the worst case there's a 50% chance that you will only need to call the constructor once, a 50% chance that you'll need to call it a second time, then steadily decreasing chance you'll need to call it more times. This doesn't take into account that there is actually a 100% chance that you need to call the constructor the first time, so my error of 0.5 was in the very first term. Thanks for the correction.
Bill the Lizard
A: 

Why not constructing a random BigInteger, then building a BigDecimal from it ? There is a constructor in BigDecimal : public BigDecimal(BigInteger unscaledVal, int scale) that seems relevant here, no ? Give it a random BigInteger and a random scale int, and you'll have a random BigDecimal. No ?

Riduidel
A: 

Compile this F# code into a DLL and you can also reference it in your C# / VB.NET programs.

type BigIntegerRandom() =
    static let internalRandom = new Random()

    /// Returns a BigInteger random number of the specified number of bytes.
    static member RandomBigInteger(numBytes:int, rand:Random) =
        let r = if rand=null then internalRandom else rand
        let bytes : byte[] = Array.zeroCreate (numBytes+1)
        r.NextBytes(bytes)
        bytes.[numBytes] <- 0uy
        bigint bytes

    /// Returns a BigInteger random number from 0 (inclusive) to max (exclusive).
    static member RandomBigInteger(max:bigint, rand:Random) =
        let rec getNumBytesInRange num bytes = if max < num then bytes else getNumBytesInRange (num * 256I) bytes+1
        let bytesNeeded = getNumBytesInRange 256I 1
        BigIntegerRandom.RandomBigInteger(bytesNeeded, rand) % max

    /// Returns a BigInteger random number from min (inclusive) to max (exclusive).
    static member RandomBigInteger(min:bigint, max:bigint, rand:Random) =
        BigIntegerRandom.RandomBigInteger(max - min, rand) + min
Chris Nash
A: 

for intervals like [0,9], [0,99], [0,999] etc...

public BigInteger getRandomBigInteger (BigInteger digits) {
    String sNumber = "";
    for (BigInteger index = BigInteger.ZERO; index.compareTo(digits) < 0; index = index.add(BigInteger.ONE) ) {
        char c = Double.toString(Math.random()*10).charAt(0);
        sNumber += c;
    }
    return new BigInteger(sNumber);
}
Alexander.Berg