Как создать случайное значение BigInteger в Java? - PullRequest
60 голосов
/ 18 февраля 2010

Мне нужно генерировать произвольно большие случайные целые числа в диапазоне от 0 (включительно) до n (исключая). Сначала я хотел вызвать nextDouble и умножить на n, но как только n станет больше 2 53 , результаты больше не будут распределяться равномерно.

BigInteger имеет следующий доступный конструктор:

public BigInteger(int numBits, Random rnd)

Создает случайно сгенерированный BigInteger, равномерно распределенный в диапазоне от 0 до (2 numBits - 1) включительно.

Как это можно использовать для получения случайного значения в диапазоне 0 - n, где n не является степенью 2?

Ответы [ 7 ]

44 голосов
/ 18 февраля 2010

Используйте цикл:

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

в среднем для этого потребуется менее двух итераций, и выбор будет равномерным.

Редактировать: Если ваш ГСЧ дорогой, вы можете ограничить количество итераций следующим образом:

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 не является исключительно медленным.

17 голосов
/ 18 февраля 2010

Следующий метод использует конструктор 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 раз .

13 голосов
/ 18 февраля 2010

Самый простой подход (довольно длинный путь) состоит в том, чтобы использовать указанный конструктор для генерации случайного числа с правильным количеством битов (floor(log2 n) + 1), а затем выбросить его, если оно больше n.В худшем из возможных случаев (например, число в диапазоне [0, 2 n + 1) вы выбросите в среднем чуть меньше половины созданных вами значений.

0 голосов
/ 30 октября 2017

Просто используйте модульное сокращение

new BigInteger(n.bitLength(), new SecureRandom()).mod(n)
0 голосов
/ 17 февраля 2011

Вот как я это делаю в классе Generic_BigInteger, доступном через: Универсальная веб-страница с исходным кодом Энди Тернера

/**
 * 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;
}
0 голосов
/ 24 апреля 2010

Скомпилируйте этот код 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
0 голосов
/ 18 февраля 2010

Почему бы не создать случайный BigInteger, а затем построить из него BigDecimal? В BigDecimal есть конструктор: public BigDecimal(BigInteger unscaledVal, int scale), который здесь уместен, нет? Дайте ему случайный BigInteger и случайный масштаб int, и вы получите случайный BigDecimal. Нет?

...