如何在Java中生成随机BigInteger值?

How to generate a random BigInteger value in Java?

我需要生成0(包含)到n(包含)范围内任意大的随机整数。我最初的想法是调用nextDouble并乘以n,但一旦n大于253,结果将不再是均匀分布的。

BigInteger具有以下构造函数:

1
public BigInteger(int numBits, Random rnd)

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

怎样才能得到0-n范围内的随机值,其中n不是2的幂?


使用循环:

1
2
3
4
BigInteger randomNumber;
do {
    randomNumber = new BigInteger(upperLimit.bitLength(), randomSource);
} while (randomNumber.compareTo(upperLimit) >= 0);

平均而言,这将需要少于两次迭代,并且选择将是一致的。

编辑:如果您的RNG很昂贵,您可以通过以下方式限制迭代次数:

1
2
3
4
5
6
7
8
int nlen = upperLimit.bitLength();
BigInteger nm1 = upperLimit.subtract(BigInteger.ONE);
BigInteger randomNumber, temp;
do {
    temp = new BigInteger(nlen + 100, randomSource);
    randomNumber = temp.mod(upperLimit);
} while (s.subtract(randomNumber).add(nm1).bitLength() >= nlen + 100);
// result is in 'randomNumber'

在这个版本中,循环被多次占用是非常不可能的(在2^100中不到一次机会,也就是说,远小于主机在接下来的一秒钟内自动着火的概率)。另一方面,mod()操作的计算代价很高,因此此版本可能比以前的版本慢,除非randomSource实例非常慢。


下面的方法使用BigInteger(int numBits, Random rnd)构造函数,如果结果大于指定的n,则拒绝该结果。

1
2
3
4
5
6
7
8
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;
}

这样做的缺点是,构造函数被调用的次数未指定,但在最坏的情况下(n只是略大于2的幂),对构造函数的预期调用次数应该只有2次左右。


最简单的方法(相当长的一段时间)是使用指定的构造函数生成一个具有正确位数的随机数(floor(log2 n) + 1),然后在大于n的情况下丢弃它。在最坏的情况下(例如范围[0,2n+1]中的一个数),平均丢弃的值不到创建值的一半。


只需使用模块化缩减

1
new BigInteger(n.bitLength(), new SecureRandom()).mod(n)


以下是我如何在一个名为generic_biginteger的类中执行此操作,该类可通过以下方式获得:安迪·特纳的通用源代码网页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
 * There are methods to get large random numbers. Indeed, there is a
 * constructor for BigDecimal that allows for this, but only for uniform
 * distributions over a binary power range.
 * @param a_Random
 * @param upperLimit
 * @return a random integer as a BigInteger between 0 and upperLimit
 * inclusive
 */

public static BigInteger getRandom(
        Generic_Number a_Generic_Number,
        BigInteger upperLimit) {
    // Special cases
    if (upperLimit.compareTo(BigInteger.ZERO) == 0) {
        return BigInteger.ZERO;
    }
    String upperLimit_String = upperLimit.toString();
    int upperLimitStringLength = upperLimit_String.length();
    Random[] random = a_Generic_Number.get_RandomArrayMinLength(
        upperLimitStringLength);
    if (upperLimit.compareTo(BigInteger.ONE) == 0) {
        if (random[0].nextBoolean()) {
            return BigInteger.ONE;
        } else {
            return BigInteger.ZERO;
        }
    }
    int startIndex = 0;
    int endIndex = 1;
    String result_String ="";
    int digit;
    int upperLimitDigit;
    int i;
    // Take care not to assign any digit that will result in a number larger
    // upperLimit
    for (i = 0; i < upperLimitStringLength; i ++){
        upperLimitDigit = new Integer(
                upperLimit_String.substring(startIndex,endIndex));
        startIndex ++;
        endIndex ++;
        digit = random[i].nextInt(upperLimitDigit + 1);
        if (digit != upperLimitDigit){
            break;
        }
        result_String += digit;
    }
    // Once something smaller than upperLimit guaranteed, assign any digit
    // between zero and nine inclusive
    for (i = i + 1; i < upperLimitStringLength; i ++) {
        digit = random[i].nextInt(10);
        result_String += digit;
    }
    // Tidy values starting with zero(s)
    while (result_String.startsWith("0")) {
        if (result_String.length() > 1) {
            result_String = result_String.substring(1);
        } else {
            break;
        }
    }
    BigInteger result = new BigInteger(result_String);
    return result;
}

为什么不先构造一个随机的biginteger,然后从中构造一个bigdecimal呢?在bigdecimal中有一个构造函数:public BigDecimal(BigInteger unscaledVal, int scale)似乎与此相关,不是吗?给它一个随机的biginteger和一个随机的scale int,你就会得到一个随机的bigdecimal。不?


将这个F代码编译成一个dll,您也可以在C/vb.net程序中引用它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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