Как создать случайное значение BigInteger в Java?
Мне нужно генерировать произвольно большие случайные числа в диапазоне 0 (включительно) до n (исключая). Моя первоначальная мысль состояла в том, чтобы вызвать nextDouble
и умножить на n, но как только n будет больше, чем 2 53 результаты больше не будут равномерно распределены.
BigInteger
имеет следующий конструктор:
public BigInteger(int numBits, Random rnd)
Создает случайно генерируемый BigInteger, равномерно распределенный в диапазоне от 0 до (2 numBits - 1) включительно.
Как это можно использовать для получения случайного значения в диапазоне 0 - n, где n не является степенью 2?
Ответы
Ответ 1
Используйте цикл:
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
является исключительно медленным.
Ответ 2
Следующий метод использует конструктор 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 раза.
Ответ 3
Простейший подход (довольно долгий путь) заключается в использовании указанного конструктора для генерации случайного числа с правильным количеством бит (floor(log2 n) + 1
), а затем выбросить его, если оно больше n. В худшем возможном случае (например, число в диапазоне [0, 2 n + 1) вы выбросите чуть меньше половины значений, которые вы создаете, в среднем.
Ответ 4
Почему бы не построить случайный BigInteger, а затем построить из него BigDecimal?
Существует конструктор в BigDecimal: public BigDecimal(BigInteger unscaledVal, int scale)
, который кажется здесь актуальным, нет? Дайте ему случайный BigInteger и случайный масштаб int, и у вас будет случайный BigDecimal. Нет?
Ответ 5
Вот как я делаю это в классе 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;
}
Ответ 6
Просто используйте модульное сокращение
new BigInteger(n.bitLength(), new SecureRandom()).mod(n)
Ответ 7
Скомпилируйте этот код F # в DLL, и вы также можете ссылаться на него в своих программах на С#/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