67

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

BigInteger有以下可用的构造函数:

public BigInteger(int numBits, Random rnd)

构造一个随机生成的 BigInteger,均匀分布在 0 到 (2 numBits - 1) 的范围内,包括 0 到 (2 numBits - 1)。

如何使用它来获得 0 - n 范围内的随机值,其中 n 不是 2 的幂?

4

9 回答 9

51

使用循环:

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

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

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

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实例特别慢。

于 2010-02-18T16:13:33.060 回答
19

以下方法使用BigInteger(int numBits, Random rnd)构造函数,如果结果大于指定的 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;
}

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

于 2010-02-18T16:14:03.887 回答
13

最简单的方法(通过相当长的方式)是使用指定的构造函数生成一个具有正确位数 ( floor(log2 n) + 1) 的随机数,然后如果它大于 n 则将其丢弃。在最坏的情况下(例如 [0, 2 n + 1 范围内的数字),平均而言,您将丢弃不到一半的值。

于 2010-02-18T16:12:02.220 回答
0

为什么不构建一个随机的 BigInteger,然后从中构建一个 BigDecimal 呢?BigDecimal 中有一个构造函数:public BigDecimal(BigInteger unscaledVal, int scale)这在这里似乎相关,不是吗?给它一个随机的 BigInteger 和一个随机的刻度 int,你就会有一个随机的 BigDecimal。不 ?

于 2010-02-18T16:31:08.660 回答
0

以下是我在名为 Generic_BigInteger 的类中的操作方法,可通过以下方式获得: Andy Turner's Generic Source Code Web Page

/**
 * 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;
}
于 2011-02-17T10:50:47.723 回答
0

对于那些仍在问这个问题并正在寻找一种在正整数范围内生成任意大随机BigInteger的方法的人,这就是我想出的。此随机生成器无需尝试一堆数字即可工作,直到其中一个适合该范围。相反,它将直接生成一个适合给定范围的随机数。

private static BigInteger RandomBigInteger(BigInteger rangeStart, BigInteger rangeEnd){
    
    Random rand = new Random();
    int scale = rangeEnd.toString().length();
    String generated = "";
    for(int i = 0; i < rangeEnd.toString().length(); i++){
        generated += rand.nextInt(10);
    }
    BigDecimal inputRangeStart = new BigDecimal("0").setScale(scale, RoundingMode.FLOOR);
    BigDecimal inputRangeEnd = new BigDecimal(String.format("%0" + (rangeEnd.toString().length()) +  "d", 0).replace('0', '9')).setScale(scale, RoundingMode.FLOOR);
    BigDecimal outputRangeStart = new BigDecimal(rangeStart).setScale(scale, RoundingMode.FLOOR);
    BigDecimal outputRangeEnd = new BigDecimal(rangeEnd).add(new BigDecimal("1")).setScale(scale, RoundingMode.FLOOR); //Adds one to the output range to correct rounding
    
    //Calculates: (generated - inputRangeStart) / (inputRangeEnd - inputRangeStart) * (outputRangeEnd - outputRangeStart) + outputRangeStart 
    BigDecimal bd1 = new BigDecimal(new BigInteger(generated)).setScale(scale, RoundingMode.FLOOR).subtract(inputRangeStart);
    BigDecimal bd2 = inputRangeEnd.subtract(inputRangeStart);
    BigDecimal bd3 = bd1.divide(bd2, RoundingMode.FLOOR);
    BigDecimal bd4 = outputRangeEnd.subtract(outputRangeStart);  
    BigDecimal bd5 = bd3.multiply(bd4);      
    BigDecimal bd6 = bd5.add(outputRangeStart);
    
    BigInteger returnInteger = bd6.setScale(0, RoundingMode.FLOOR).toBigInteger();
    returnInteger = (returnInteger.compareTo(rangeEnd) > 0 ? rangeEnd : returnInteger); //Converts number to the end of output range if it's over it. This is to correct rounding.
    return returnInteger;
}   

它是如何工作的?

首先,它生成一个带有与最大范围相同长度的随机数的字符串。例如:给定范围为 10-1000 时,它将生成 0000 到 9999 之间的某个数字作为String

然后它创建BigDecimals来表示最大可能值(在前面的示例中为 9999)和最小值 (0),并将范围参数BigIntegers转换为BigDecimals。同样在此步骤中,给定范围的最大值加 1,以便在下一步中纠正舍入误差。

然后使用此公式将生成的随机数映射到给定范围:

(generated - inputRangeStart) / (inputRangeEnd - inputRangeStart) * (outputRangeEnd - outputRangeStart) + outputRangeStart

之后,它将最后检查映射的数字是否适合给定范围,如果不适合,则将其设置为给定范围最大值。这样做是为了纠正舍入误差。

于 2022-01-06T12:41:05.420 回答
-1

只需使用模块化减少

new BigInteger(n.bitLength(), new SecureRandom()).mod(n)
于 2017-10-30T14:32:11.177 回答
-2

如果我们想生成随机的 70 位 biginteger,我们可以生成 byte[70] 数组并用 0 到 9 的随机数填充它并添加 48 以生成数字字符并将数字字符转换为 biginteger

public BigInteger RandomBigInteger(int digits_len) {
    Random rnd = new Random();
    byte[]num = new byte[digits_len];
    if(digits_len>1){//first digit must not be 0 because 0555 will be 555
        num[0]=(byte)((((int)(rnd.nextFloat()*10))%9)+1+48);//([*10 means] rnd 0-9),([%9 means] 0-8 , [+1 means] 1-9) ,([+48 means] 0->48,1->49)
        for(int i=1;i<num.length;i++){ num[i]=(byte)((((int)(rnd.nextFloat()*10))%10)+48); }//([*10 means] rnd 0-9),([%10 means] 0-9 because rnd may be 1.000*10=10) ,([+48 means] 0->48,1->49)
    }else{//digits_len = 1 , it can be 0-9
        num[0]=(byte)((((int)(rnd.nextFloat()*10))%10)+48);//([*10 means] rnd 0-9),([%10 means] 0-9 because rnd may be 1.000*10=10) ,([+48 means] 0->48,1->49)
    }
    return new BigInteger(new String(num,StandardCharsets.ISO_8859_1),10);//convert the digit chars to biginteger
}

BigInteger num = RandomBigInteger(5);//num = 12345
于 2020-05-18T18:47:23.433 回答
-4

将此 F# 代码编译为 DLL,您也可以在 C# / VB.NET 程序中引用它

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
于 2010-04-23T23:57:33.820 回答