Search code examples
javarandombiginteger

Create random BigInteger using RandomGenerator (introduced in Java 17)


The class BigInteger provides the constructor

public BigInteger(int numBits, Random rnd)

Since Java 17 the use of RandomGenerator is encouraged. How can I generate a random BigInteger in the range 0...(2^n)-1 using only an instance of RandomGenerator.


Solution

  • Just using Random & SecureRandom

    Why do you need this? Because the new Random() gets you a relatively fast non-secure random number generator and new SecureRandom() provides a CSPRNG (Cryptographically Secure Pseudo Random Number Generator). Both those instances are instances of the RandomGenerator interface. So the only time you'd need this is if you want a specific Random generator defined in the random package.

    If you don't need speed and you haven't any specific needs regarding the distribution - you'd probably know if you'd have any - then please just use new BigInteger(n, new SecureRandom()).

    Implementing Random

    TL;DR: don't

    As RandomGenerator is an interface. The Random class has been refactored into a class that implements the interface. That means you can use the old Random wherever an interface specifies RandomGenerator. However, that doesn't work the other way around.

    Random itself is not a final class, so it can be inherited from. So you could, in principle, create an adapter class by simply implementing all methods in Random and calling the similarly named methods of a wrapped RandomGenerator. This is however very dangerous practice as future extensions of Random may break everything. In the worst case it will show inconsistent behavior by mixing the state of the parent Random object and the wrapped RandomGenerator.

    Move to Java 19

    In Java 19 (build b21) there is an adapter method provided for RandomGenerator named Random#from(RandomGenerator). You can see the feature request here. Note that this is an intermediate step; preferably the BigInteger method should be retrofitted to use RandomGenerator (as stated in the feature request).

    Beware that you should never use this wrapper class if you suspect that setSeed can ever be called, e.g. when generating a large prime in the BigInteger class. If it is called it will generate an UnsupportedOperationException.

    These kind of classes go through all kinds of testing so the Java provided method can be trusted. However, the fact that a method can be retrofitted to use setSeed could be a problem regarding forward compatibility - you have been warned.

    The special case of [0, n^2)

    However, you've got one out: the range 0..2^n-1 or [0, 2^n-1) is a number consisting of a set of bits. So what you can do is use your RandomGenerator, fill an array of bytes, remove the most significant bits from the first byte (as Java is big endian), and then convert the array into a signed integer:

    /**
     * Mimics the {@link BigInteger#BigInteger(int, Random)} function using a
     * {@link RandomGenerator} instance.
     * 
     * @param numBits maximum bitLength of the new BigInteger.
     * @param rnd     source of randomness to be used in computing the new
     *                BigInteger.
     * @throws IllegalArgumentException {@code numBits} is negative.
     * @see #bitLength()
     */
    public static BigInteger generateRandomBigInteger(int numBits, RandomGenerator rng) {
        if (numBits < 0) {
            throw new IllegalArgumentException("numBits must be non-negative");
        }
    
        if (numBits == 0) {
            return BigInteger.ZERO;
        }
    
        int bytes = (numBits + Byte.SIZE - 1) / Byte.SIZE;
        // mask bits that we need the value of to 1, the others - if any -- will be set
        // to zero
        byte bitMask = (byte) ((1 << ((numBits - 1) % Byte.SIZE + 1)) - 1);
        byte[] randomBytes = new byte[bytes];
        rng.nextBytes(randomBytes);
        randomBytes[0] &= bitMask;
        return new BigInteger(1, randomBytes);
    }
    

    numBits is of course the same as your n variable.

    The case for [0, max)

    Actually, you can combine the comparison function and the random bit generator in such a way that you can create large random values in the range 0..max with max having any value, with a minimum amount of random bits and no nasty BigInteger operations (such as division) at all.

    You can find my implementation of that here but you may get weirded out. Sorry in advance, and yes, this is a shameless plug of my RNGBC algorithm :)